近年来,对 Python 编码技能的需求不断增长。为帮助您提升 Python 编程能力,我们精选了 30 个酷炫的 Python 技巧,您可以用来改进代码 。接下来 30 天里每天学习一个,并查看我们的Python 最佳实践 文章,确保您的代码达到一流水准。
如果您的 Python 基础还不够扎实,也可以通过我们的 Python Programming 技能学习路径来强化。
序列与数据结构技巧
#1 切片
a = "Hello World!"
print ( a[ : : - 1 ] )
"""
!dlroW olleH
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
切片是 Python 中基于索引来访问序列子集的特性。索引就是元素在序列中的位置。如果序列类型是可变的,您可以使用切片来提取并修改数据。
注意 :我们也可以对不可变序列进行切片,但尝试修改切片会引发 TypeError。
切片的实现格式为:sequence[start:stop:step] 。如果未在 start 、stop 和 step 参数中指定值,则序列会使用默认值。默认值为:
“start” 默认为 0
“stop” 默认为序列长度
“step” 未指定时默认为 1。
当使用 sequence[start:stop] 时,返回的元素为从起始索引到 stop - 1(不包含 stop 索引)。
我们也可以传入负索引,用于反向访问序列。例如,在一个包含 4 个元素的列表中,索引 0 也可以写作 -4,最后一个索引也可以写作 -1。在上面的示例代码中,这一技巧应用到了序列的 step 参数。因此,字符串从序列末尾到索引 0 被倒序打印。
#2 就地交换 / 同时赋值
a = 10
b = 5
print ( f"First: { a, b} " )
"""
First: (10, 5)
"""
a, b = b, a + 2
print ( f"Second: { a, b} " )
"""
Second: (5, 12)
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
如果您最初以为 b 的值会是 7 而不是 12,那么就中了就地交换的“陷阱”。
在 Python 中,我们可以使用自动解包在一次赋值中将可迭代对象解包到变量。例如:
a, b, c = [ 1 , 2 , 3 ]
print ( a)
print ( b)
print ( c)
"""
1
2
3
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
我们也可以使用 * 将多个值收集到一个变量中——这种技巧称为打包。下面是一个打包的示例。
a, * b = 1 , 2 , 3
print ( a, b)
"""
1 [2, 3]
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
结合自动打包与解包就形成了同时赋值技巧。我们可以用它将一系列值同时赋给一系列变量。
#3 列表 vs. 元组
import sys
a = [ 1 , 2 , 3 , 4 , 5 ]
b = ( 1 , 2 , 3 , 4 , 5 )
print ( f"List size: { sys. getsizeof( a) } bytes" )
print ( f"Tuple size: { sys. getsizeof( b) } bytes" )
"""
List size: 52 bytes
Tuple size: 40 bytes
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
大多数 Python 程序员都熟悉列表这种数据结构,但对元组 却未必。二者都是可迭代的、允许索引,并可存储多种数据类型。但在某些场景下,相比列表更应优先使用元组。
首先,列表是可变的,这意味着我们可以按需修改它:
a = [ 1 , 2 , 3 , 4 , 5 ]
a[ 2 ] = 8
print ( a)
"""
[1,2,8,4,5]
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
另一方面,元组是不可变的,这意味着尝试修改它会引发 TypeError 。
因此,从内存角度看,元组更高效,因为 Python 可以为数据分配恰当的内存块。相反,列表为了可能被扩展而需要额外预留内存——这称为动态内存分配。
简而言之 :在不希望数据被更改的场景下,出于内存原因应优先选择元组而非列表。元组也比列表更快。
在本教程中进一步了解 Python 数据结构 。
#4 生成器
a = [ x * 2 for x in range ( 10 ) ]
b = ( x * 2 for x in range ( 10 ) )
print ( a)
print ( b)
"""
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
<generator object <genexpr> at 0x7f61f8808b50>
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
列表推导式是 Pythonic 的从其他可迭代对象创建列表的方法——它比 for 循环快得多。但如果您不小心把 [] 改成了 () 会怎样?您会得到一个生成器对象。
在 Python 中,用圆括号包裹推导逻辑会创建所谓的生成器对象。生成器是一类特殊的可迭代对象。与列表不同,它不存储所有元素,而是存储生成各元素的指令以及当前迭代状态。
每个元素仅在被请求时才会生成,这种技术称为惰性求值 。使用生成器的主要好处是占用更少内存,因为不会一次性构建整个序列。
#5 别名
a = [ 1 , 2 , 3 , 4 , 5 ]
b = a
b[ 4 ] = 7
print ( id ( a) )
print ( id ( b) )
print ( a)
"""
15136008
15136008
[1, 2, 3, 4, 7]
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
Python 是面向对象的编程语言——一切皆对象。因此,将对象赋给标识符实际上是在创建对该对象的引用。
当我们把一个标识符赋给另一个标识符时,就得到两个引用同一对象的标识符。这就是所谓的别名。对一个别名的更改会影响另一个。有时这种行为是期望的,但往往会让人措手不及。
一种规避方法是在使用可变对象时避免创建别名。另一个解决方案是创建原对象的克隆,而不是引用。
最直接创建克隆的方法是利用切片:
b = a[:] 技术支持
这将在标识符 b 中创建一个新的列表对象引用。
您还可以采用许多其他方案,例如在赋值给另一个标识符时调用 list(a) ,或使用 copy() 方法。
#6 ‘not’ 运算符
a = [ ]
print ( not a)
"""
True
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
接下来这个 Python 小技巧是使用 not 运算符来最简单地检查数据结构是否为空。Python 内置的 not 是逻辑运算符,如果表达式不为真则返回 True,否则返回 False——它会对布尔表达式和对象的真值取反。
您还可能在 if 语句中见到它的用法:
if not a:
# do something... 技术支持
当 a 为 True 时,not 运算符会返回 False ,反之亦然。
乍一看可能有点绕,不妨自己试一试。
字符串与输出技巧
#7 F-strings
first_name = "John"
age = 19
print ( f"Hi, I'm { first_name} and I'm { age} years old!" )
"""
Hi, I'm John and I'm 19 years old!
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
有时我们需要格式化字符串对象;Python 3.6 引入了名为 f-strings 的酷炫特性来简化这一过程。要更好地体会新方法的优点,了解新版本之前的格式化方式会有所帮助。
过去字符串的格式化方式如下:
first_name = "John"
age = 19
print ( "Hi, I'm {} and I'm {} years old!" . format ( first_name, age) )
"""
Hi, I'm John and I'm 19 years old!
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
总体而言,新格式化方式更快、更易读、更简洁,也更不容易出错。
f-strings 的另一个用法是同时打印标识符名与其值。这在 Python 3.8 中引入。
x = 10
y = 20
print ( f" { x = } , { y = } " )
"""
x = 10, y = 20
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
查看本教程以了解更多:Python 中的 F-string 格式化 。
#8 print() 函数的 ‘end’ 参数
languages = [ "english" , "french" , "spanish" , "german" , "twi" ]
print ( ' ' . join( languages) )
"""
english french spanish german twi
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
我们经常在不指定任何可选参数的情况下使用 print 语句。因此,很多 Pythonista 并不知道我们可以在一定程度上控制输出。
一个可更改的可选参数是 end 。end 参数指定在一次 print 调用的末尾应显示什么。
end 的默认值为 "\n" ,表示换行。在上述代码中,我们将其改为空格。因此,输出把列表的所有元素打印在同一行。
#9 向元组“追加”
a = ( 1 , 2 , [ 1 , 2 , 3 ] )
a[ 2 ] . append( 4 )
print ( a)
"""
(1, 2, [1, 2, 3, 4])
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
我们已经知道元组是不可变的——参见技巧 #3 列表 vs. 元组。尝试改变元组的状态会抛出 TypeError 。但是,如果把元组对象看作是“名称序列绑定到对象且绑定不可改变”,您可能会有不同的认识。
我们元组的前两个元素是整数——它们是不可变的。最后一个元素是列表,这是 Python 中的可变对象。
如果我们把这个列表视为序列中的另一个“名称”,其绑定的对象不能被替换,那么就会意识到:我们仍然可以在元组内部修改该列表。
我们会建议在实践中这么做吗?大概不会,但这是一个“知道也不错”的知识点!
#10 合并字典
a = { "a" : 1 , "b" : 2 }
b = { "c" : 3 , "d" : 4 }
a_and_b = a | b
print ( a_and_b)
"""
{"a": 1, "b": 2, "c": 3, "d": 4}
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
在 Python 3.9 及以上版本,可以使用 | (按位或)来合并字典。对此技巧也没太多可补充的——就是更易读!
代码风格与语法技巧
#11 三元运算符 / 条件表达式
condition = True
name = "John" if condition else "Doe"
print ( name)
"""
John
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
上面的代码展示了所谓的三元运算符——也称作条件表达式。我们用三元运算符根据条件是 True 还是 False 来进行求值。
我们也可以这样写:
condition = True
if condition:
name = "John"
else :
name = "Doe"
print ( name)
"""
John
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
尽管两段代码输出相同,但请注意三元条件表达式让代码更短更清晰。这就是 Pythonista 所说的更“Pythonic”的写法。
#12 移除列表中的重复项
a = [ 1 , 1 , 2 , 3 , 4 , 5 , 5 , 5 , 6 , 7 , 2 , 2 ]
print ( list ( set ( a) ) )
"""
[1, 2, 3, 4, 5, 6, 7]
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
移除列表中重复元素的最简单方法是将列表转换为集合(如有需要再转回列表)。
从可变性角度看,集合和列表在 Python 中挺相似。我们可以随意向两者添加和移除元素,但二者仍非常不同。
列表是有序的、从 0 开始索引且可变。集合是无序且无索引的。集合中的元素必须是不可变类型,尽管集合本身是可变的——尝试通过索引检索元素或修改元素会引发错误。
集合与列表的另一个关键区别是集合不能包含重复项。这正是帮助我们移除列表重复元素的原因。
#13 独立的下划线
>> > print( _)
Traceback ( most recent call last) :
File "<stdin>" , line 1 , in < module>
>> > 1 + 2
3
>> > print( _)
3 技术支持
下划线(_)在 Python 中是合法的标识符,因此可以用来引用对象。但下划线还有另一职责:保存上一次求值的结果。
文档指出:“交互式解释器会将上一次求值的结果存放在变量 _ 中。(它存放在 builtins 模块中,与 print 等内置函数在一起。)”
由于我们在第一行调用它之前并未给下划线赋值,因此报了错。然而当我们计算 1 + 2 的结果后,交互式解释器就将结果存放在 _ 标识符中。
#14 用下划线忽略值
for _ in range ( 100 ) :
print ( "The index doesn't matter" )
"""
The index doesn't matter
The index doesn't matter
...
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
在技巧 #13 中,我们知道交互式解释器会把上次求值结果放入下划线(_)标识符,但它并非唯一用法。
我们也可用它来表示我们不关心或之后不会使用的对象。这很重要,因为如果用普通标识符而不是下划线(_),在对程序进行代码规范检查时会触发 F841 错误。F841 错误表示某个局部变量被赋值但未使用,这是不良实践。
#15 结尾下划线
list_ = [ 0 , 1 , 2 , 3 , 4 ]
global_ = "Hi there" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
延续前两条关于下划线(_)的技巧,它的另一个用途是避免与 Python 关键字冲突。
PEP 8 提到,结尾下划线应“按惯例用于避免与 Python 关键字冲突”。它还指出,“通常最好追加一个结尾下划线,而不是使用缩写或拼写变体。因此 list_ 比 lst 更好。”
#16 开头下划线
class Example :
def __init__ ( self) :
self. _internal = 2
self. external = 20 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
您经常会看到有经验的 Python 程序员在标识符或方法名前加下划线——自有其道理。
前缀下划线隐含的意义是:此变量或方法仅供内部使用。本质上,这是对其他程序员的声明,写入了 PEP 8,但并未被 Python 强制执行。因此,前导下划线只是弱指示。
不同于 Java,Python 并没有对私有与公有变量的强区分。换言之,它之所以有意义,只是因为 Python 社区约定了它的含义。它们的加入并不会影响程序的行为。
#17 下划线分隔数字
这是关于下划线的最后一个技巧;到目前为止我们涵盖了三种不同的用法,您也可以查看教程进一步了解 Python 中下划线(_)的作用 。
number = 1_500_000
print ( number)
"""
15000000
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
我们还可以将下划线用作整数、浮点数和复数字面量中的数字分组视觉分隔符——这是在 Python 3.6 中引入的。
其目的是提高长数字或应清晰分段的数字的可读性——详见 PEP 515。
代码风格与语法技巧
#18 __name__ == “__main__”
if __name__ == "__main__" :
print ( "Read on to understand what is going on when you do this." )
"""
print("Read on to understand what is going on when you do this.")
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
您很可能在许多 Python 程序中见过这种语法;当正在运行的 Python 文件是主程序时,Python 会使用一个特殊名称 "__main__" 并将其赋给标识符 __name__ 。
如果我们决定将截图中的模块(Python 文件)导入到另一个模块中并运行该文件,那么代码中表达式的真假将为假。这是因为当我们从另一个模块导入时,__name__ 会被设为该模块(Python 文件)的名称。
#19 ‘setdefault’ 方法
import pprint
text = "It's the first of April. It's still cold in the UK. But I'm going to the museum so it should be a wonderful day"
counts = { }
for word in text. split( ) :
counts. setdefault( word, 0 )
counts[ word] += 1
pprint. pprint( counts)
"""
{'April.': 1,
'But': 1,
"I'm": 1,
"It's": 2,
'UK.': 1,
'a': 1,
'be': 1,
'cold': 1,
'day': 1,
'first': 1,
'going': 1,
'in': 1,
'it': 1,
'museum': 1,
'of': 1,
'should': 1,
'so': 1,
'still': 1,
'the': 3,
'to': 1,
'wonderful': 1}
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
您可能希望为字典中的多个键设置值,例如统计语料库中的词频。常见做法如下:
检查键是否存在于字典中
如果存在,将其值加 1。
如果不存在,则添加该键并将值设为 1。
代码如下所示:
counts = { }
for word in text. split( ) :
if word in counts:
counts[ word] += 1
else :
counts[ word] = 1 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
更简洁的做法是对字典对象使用 setdefault() 方法。
传给该方法的第一个参数是我们要检查的键。第二个参数是在键不存在时应设置的值——如果键已存在,则方法会返回该键的值,从而不会被修改。
程序结构技巧
#20 正则匹配
import re
number = re. compile ( r"(0)?(\+44)?\d{10}" )
num_1 = number. search( "My number is +447999999999" )
num_2 = number. search( "My number is 07999999999" )
print ( num_1. group( ) )
print ( num_2. group( ) )
"""
'+447999999999'
'07999999999'
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
正则表达式 允许您指定要搜索的文本模式;大多数人都知道可以使用 Ctrl+F(Windows)来搜索,但如果您并不清楚要找的精确内容,该如何找到?答案是搜索“模式”。
例如,英国号码通常遵循相似的模式:要么以 0 开头后接十个数字,要么用 +44 代替开头的 0 后接十个数字——第二种是国际格式。
正则表达式能大幅节省时间。如果不用正则而是编写规则来捕获示例中的情况,可能需要 10 多行代码。
即使您不写代码,了解正则表达式的工作原理也很重要。大多数现代文本编辑器和文字处理器的查找替换功能都允许使用正则表达式。
#21 正则中的管道符
import re
heros = re. compile ( r"Super(man|woman|human)" )
h1 = heros. search( "This will find Superman" )
h2 = heros. search( "This will find Superwoman" )
h3 = heros. search( "This will find Superhuman" )
print ( h1. group( ) )
print ( h2. group( ) )
print ( h3. group( ) )
"""
Superman
Superwoman
Superhuman
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
正则表达式有一个特殊字符称为管道符(| ),允许您在多个表达式中匹配其一,且可以在任意位置使用。当您有多个相似的模式时,这非常方便。
例如,“Superman”“Superwoman”“Superhuman”都有相同前缀。因此您可以利用管道符保留模式中重复的部分,替换需要不同的部分,再次为您节省宝贵时间。
注意坑点 :如果您要匹配的所有表达式都出现在同一文本中,将返回第一个匹配出现的位置——例如“An example text containing Superwoman, Superman, Superhuman”会返回 Superwoman。
#22 print() 函数的 ‘sep’ 参数
day = "04"
month = "10"
year = "2022"
print ( day, month, year)
print ( day, month, year, sep = "" )
print ( day, month, year, sep = "." )
"""
04 10 2022
04/10/2022
04.10.2022
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
不知道 print() 函数全部能力的 Python 程序员数量之多,着实让人吃惊;如果“Hello World”是您的第一个程序,那么 print() 可能是您学习 Python 时最先接触的内置函数之一。我们用 print() 在屏幕上显示格式化信息,但它的功能远不止于此。
在上面的代码中,我们展示了几种不同的格式化输出方式。sep 参数是 print() 函数的可选参数,用于指定当包含多个对象时,它们之间应如何分隔。
默认是用空格分隔,但我们在打印语句中更改了这一行为——一个将 sep 设为 "" ,另一个将其设为 "." 。
#23 Lambda 函数
def square ( num: int ) - > int :
return num ** 2
print ( f"Function call: { square( 4 ) } " )
"""
Function call: 16
"""
square_lambda = lambda x: x** 2
print ( f"Lambda function: { square_lambda( 4 ) } " )
"""
Lambda functional: 16
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
Lambda 函数 带您进入可以用 Python 完成的中高级技巧——通过本课程学习中级 Python 。它们乍看复杂,其实很简单。
在示例代码中我们只用到了一个参数,但如有需要也可以使用多个参数:
square = lambda a, b: a ** b
print ( f"Lambda function: { square( 4 , 2 ) } " )
"""
16
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
本质上,lambda 关键字允许我们在一行内创建小型、受限、匿名的函数。它们的行为与用 def 声明的常规函数相同,只不过这类函数没有名字。
#24 ‘swapcase’ 方法
string = "SoMe RaNDoM sTriNg"
print ( string. swapcase( ) )
"""
sOmE rAndOm StRInG
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
swapcase() 方法用于对字符串对象进行大小写互换:将大写变小写、将小写变大写,而且只需一行代码。此方法的用例不多,但知道也无妨。
#25 ‘isalnum’ 方法
password = "ABCabc123"
print ( password. isalnum( ) )
"""
True
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
假设我们要编写一个需要用户输入密码的程序,并要求密码必须由数字和字母组合而成。我们可以在一行代码中通过调用字符串实例的 isalnum() 来实现。
该方法会检查所有字符是否均为字母(A-Za-z)和数字(0-9)。包含空格或符号(!#%$&? 等)会返回 False 。
#26 异常处理
def get_ration ( x: int , y: int ) - > int :
try :
ratio = x/ y
except ZeroDivisionError:
y = y + 1
ratio = x/ y
return ratio
print ( get_ration( x= 400 , y= 0 ) )
"""
400.0
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
Python 程序在遇到错误时会终止。
有时我们并不希望出现这种行为,比如当终端用户与我们的代码进行交互时。如果在这种情况下程序过早终止,会有多糟?
处理异常情况有几种思想流派。多数 Python 程序员通常拥抱“先斩后奏比事先请示更容易 ”的理念。这意味着他们更倾向于通过提供能够处理异常的上下文来捕获已引发的错误。这一思想背后的理由是:没必要把时间浪费在为各种可能的异常情况设置防护上。
但这只在问题发生后确有应对机制时才成立。
#27 识别列表差异
list_1 = [ 1 , 3 , 5 , 7 , 8 ]
list_2 = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]
solution_1 = list ( set ( list_2) - set ( list_1) )
solution_2 = list ( set ( list_1) ^ set ( list_2) )
solution_3 = list ( set ( list_1) . symmetric_difference( set ( list_2) ) )
print ( f"Solution 1: { solution_1} " )
print ( f"Solution 2: { solution_2} " )
print ( f"Solution 3: { solution_3} " )
"""
Solution 1: [9, 2, 4, 6]
Solution 2: [2, 4, 6, 9]
Solution 3: [2, 4, 6, 9]
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
这里提供三种在 Python 中比较两个列表差异的方法。
注意 :除非您确定 list_1 是 list_2 的子集,否则解法 1 与后两种解法并不相同。
#28 Args 与 kwargs
def some_function ( * args, ** kwargs) :
print ( f"Args: { args} " )
print ( f"Kwargs: { kwargs} " )
some_function( 1 , 2 , 3 , a= 4 , b= 5 , c= 6 )
"""
Args: (1, 2, 3)
Kwargs: {'a': 4, 'b': 5, 'c': 6}
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
当我们不确定函数应接收多少参数时,会将 *args 与 **kwargs 作为函数参数。
*args 允许在参数为非关键字(即不需要关联名称)时,将可变数量的参数传入函数。相对地,**kwargs 允许将任意数量的关键字参数传入函数。
实际上,*args 与 **kwargs 这两个词并不神奇:真正的“魔法”在星号(*)上。这意味着星号后的词可以任意取名,但使用 args 与 kwargs 是常见惯例,并已在 Python 开发者中约定俗成。
#29 省略号
print ( . . . )
"""
Ellipsis
"""
def some_function ( ) :
. . .
def another_function ( ) :
pass 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
省略号(Ellipsis)是一个 Python 对象,可以通过连续三个点(...)或直接调用对象(Ellipsis)来表示。
它最著名的用途是在 NumPy 中访问和切片多维数组,例如:
import numpy as np
arr = np. array( [ [ 2 , 3 ] , [ 1 , 2 ] , [ 9 , 8 ] ] )
print ( arr[ . . . , 0 ] )
"""
[2 1 9]
"""
print ( arr[ . . . ] )
"""
[[2 3]
[1 2]
[9 8]]
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
省略号的另一个用法是在未实现的函数中充当占位符。
这意味着您可以使用 Ellipsis、... 或 pass,它们都同样有效。
#30 列表推导式
even_numbers = [ x for x in range ( 10 ) if x % 2 == 0 and x != 0 ]
print ( even_numbers)
"""
[2, 4, 6, 8]
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
最后一个 Python 技巧是列表推导式 ,它是一种从其他序列优雅构建列表的方式。正如上面的代码所示,它允许您执行复杂的逻辑与过滤。
还有其他方法可以实现相同目标;例如,我们可以像下面这样使用 lambda 函数:
even_numbers = list ( filter ( lambda x: x % 2 == 0 and x != 0 , range ( 10 ) ) )
print ( even_numbers)
"""
[0, 2, 4, 6, 8]
""" 技术支持 示例学习编辑器,按 Tab 键进入。按 Escape 键然后按 Tab 或 Shift+Tab 导航退出。
但许多 Pythonista 会认为这种方案相比列表推导式可读性差得多。
近年来,对 Python 编码技能的需求不断增长。为帮助您提升 Python 编程能力,我们精选了 30 个酷炫的 Python 技巧,您可以用来改进代码。接下来 30 天里每天学习一个,并查看我们的Python 最佳实践文章,确保您的代码达到一流水准。
如果您的 Python 基础还不够扎实,也可以通过我们的 Python Programming 技能学习路径来强化。
序列与数据结构技巧
#1 切片
切片是 Python 中基于索引来访问序列子集的特性。索引就是元素在序列中的位置。如果序列类型是可变的,您可以使用切片来提取并修改数据。
注意:我们也可以对不可变序列进行切片,但尝试修改切片会引发 TypeError。
切片的实现格式为:sequence[start:stop:step]。如果未在 start、stop 和 step 参数中指定值,则序列会使用默认值。默认值为:
当使用 sequence[start:stop] 时,返回的元素为从起始索引到 stop - 1(不包含 stop 索引)。
我们也可以传入负索引,用于反向访问序列。例如,在一个包含 4 个元素的列表中,索引 0 也可以写作 -4,最后一个索引也可以写作 -1。在上面的示例代码中,这一技巧应用到了序列的 step 参数。因此,字符串从序列末尾到索引 0 被倒序打印。
#2 就地交换 / 同时赋值
如果您最初以为 b 的值会是 7 而不是 12,那么就中了就地交换的“陷阱”。
在 Python 中,我们可以使用自动解包在一次赋值中将可迭代对象解包到变量。例如:
我们也可以使用 * 将多个值收集到一个变量中——这种技巧称为打包。下面是一个打包的示例。
结合自动打包与解包就形成了同时赋值技巧。我们可以用它将一系列值同时赋给一系列变量。
#3 列表 vs. 元组
大多数 Python 程序员都熟悉列表这种数据结构,但对元组却未必。二者都是可迭代的、允许索引,并可存储多种数据类型。但在某些场景下,相比列表更应优先使用元组。
首先,列表是可变的,这意味着我们可以按需修改它:
另一方面,元组是不可变的,这意味着尝试修改它会引发 TypeError。
因此,从内存角度看,元组更高效,因为 Python 可以为数据分配恰当的内存块。相反,列表为了可能被扩展而需要额外预留内存——这称为动态内存分配。
简而言之:在不希望数据被更改的场景下,出于内存原因应优先选择元组而非列表。元组也比列表更快。
在本教程中进一步了解 Python 数据结构。
#4 生成器
列表推导式是 Pythonic 的从其他可迭代对象创建列表的方法——它比 for 循环快得多。但如果您不小心把 [] 改成了 () 会怎样?您会得到一个生成器对象。
在 Python 中,用圆括号包裹推导逻辑会创建所谓的生成器对象。生成器是一类特殊的可迭代对象。与列表不同,它不存储所有元素,而是存储生成各元素的指令以及当前迭代状态。
每个元素仅在被请求时才会生成,这种技术称为惰性求值。使用生成器的主要好处是占用更少内存,因为不会一次性构建整个序列。
#5 别名
Python 是面向对象的编程语言——一切皆对象。因此,将对象赋给标识符实际上是在创建对该对象的引用。
当我们把一个标识符赋给另一个标识符时,就得到两个引用同一对象的标识符。这就是所谓的别名。对一个别名的更改会影响另一个。有时这种行为是期望的,但往往会让人措手不及。
一种规避方法是在使用可变对象时避免创建别名。另一个解决方案是创建原对象的克隆,而不是引用。
最直接创建克隆的方法是利用切片:
这将在标识符 b 中创建一个新的列表对象引用。
您还可以采用许多其他方案,例如在赋值给另一个标识符时调用 list(a),或使用 copy() 方法。
#6 ‘not’ 运算符
接下来这个 Python 小技巧是使用 not 运算符来最简单地检查数据结构是否为空。Python 内置的 not 是逻辑运算符,如果表达式不为真则返回 True,否则返回 False——它会对布尔表达式和对象的真值取反。
您还可能在 if 语句中见到它的用法:
当 a 为 True 时,not 运算符会返回 False,反之亦然。
乍一看可能有点绕,不妨自己试一试。
字符串与输出技巧
#7 F-strings
有时我们需要格式化字符串对象;Python 3.6 引入了名为 f-strings 的酷炫特性来简化这一过程。要更好地体会新方法的优点,了解新版本之前的格式化方式会有所帮助。
过去字符串的格式化方式如下:
总体而言,新格式化方式更快、更易读、更简洁,也更不容易出错。
f-strings 的另一个用法是同时打印标识符名与其值。这在 Python 3.8 中引入。
查看本教程以了解更多:Python 中的 F-string 格式化。
#8 print() 函数的 ‘end’ 参数
我们经常在不指定任何可选参数的情况下使用 print 语句。因此,很多 Pythonista 并不知道我们可以在一定程度上控制输出。
一个可更改的可选参数是 end。end 参数指定在一次 print 调用的末尾应显示什么。
end 的默认值为 "\n",表示换行。在上述代码中,我们将其改为空格。因此,输出把列表的所有元素打印在同一行。
#9 向元组“追加”
我们已经知道元组是不可变的——参见技巧 #3 列表 vs. 元组。尝试改变元组的状态会抛出 TypeError。但是,如果把元组对象看作是“名称序列绑定到对象且绑定不可改变”,您可能会有不同的认识。
我们元组的前两个元素是整数——它们是不可变的。最后一个元素是列表,这是 Python 中的可变对象。
如果我们把这个列表视为序列中的另一个“名称”,其绑定的对象不能被替换,那么就会意识到:我们仍然可以在元组内部修改该列表。
我们会建议在实践中这么做吗?大概不会,但这是一个“知道也不错”的知识点!
#10 合并字典
在 Python 3.9 及以上版本,可以使用 |(按位或)来合并字典。对此技巧也没太多可补充的——就是更易读!
代码风格与语法技巧
#11 三元运算符 / 条件表达式
上面的代码展示了所谓的三元运算符——也称作条件表达式。我们用三元运算符根据条件是 True 还是 False 来进行求值。
我们也可以这样写:
尽管两段代码输出相同,但请注意三元条件表达式让代码更短更清晰。这就是 Pythonista 所说的更“Pythonic”的写法。
#12 移除列表中的重复项
移除列表中重复元素的最简单方法是将列表转换为集合(如有需要再转回列表)。
从可变性角度看,集合和列表在 Python 中挺相似。我们可以随意向两者添加和移除元素,但二者仍非常不同。
列表是有序的、从 0 开始索引且可变。集合是无序且无索引的。集合中的元素必须是不可变类型,尽管集合本身是可变的——尝试通过索引检索元素或修改元素会引发错误。
集合与列表的另一个关键区别是集合不能包含重复项。这正是帮助我们移除列表重复元素的原因。
#13 独立的下划线
下划线(_)在 Python 中是合法的标识符,因此可以用来引用对象。但下划线还有另一职责:保存上一次求值的结果。
文档指出:“交互式解释器会将上一次求值的结果存放在变量 _ 中。(它存放在 builtins 模块中,与 print 等内置函数在一起。)”
由于我们在第一行调用它之前并未给下划线赋值,因此报了错。然而当我们计算 1 + 2 的结果后,交互式解释器就将结果存放在 _ 标识符中。
#14 用下划线忽略值
在技巧 #13 中,我们知道交互式解释器会把上次求值结果放入下划线(_)标识符,但它并非唯一用法。
我们也可用它来表示我们不关心或之后不会使用的对象。这很重要,因为如果用普通标识符而不是下划线(_),在对程序进行代码规范检查时会触发 F841 错误。F841 错误表示某个局部变量被赋值但未使用,这是不良实践。
#15 结尾下划线
延续前两条关于下划线(_)的技巧,它的另一个用途是避免与 Python 关键字冲突。
PEP 8 提到,结尾下划线应“按惯例用于避免与 Python 关键字冲突”。它还指出,“通常最好追加一个结尾下划线,而不是使用缩写或拼写变体。因此 list_ 比 lst 更好。”
#16 开头下划线
您经常会看到有经验的 Python 程序员在标识符或方法名前加下划线——自有其道理。
前缀下划线隐含的意义是:此变量或方法仅供内部使用。本质上,这是对其他程序员的声明,写入了 PEP 8,但并未被 Python 强制执行。因此,前导下划线只是弱指示。
不同于 Java,Python 并没有对私有与公有变量的强区分。换言之,它之所以有意义,只是因为 Python 社区约定了它的含义。它们的加入并不会影响程序的行为。
#17 下划线分隔数字
这是关于下划线的最后一个技巧;到目前为止我们涵盖了三种不同的用法,您也可以查看教程进一步了解 Python 中下划线(_)的作用。
我们还可以将下划线用作整数、浮点数和复数字面量中的数字分组视觉分隔符——这是在 Python 3.6 中引入的。
其目的是提高长数字或应清晰分段的数字的可读性——详见 PEP 515。
代码风格与语法技巧
#18 __name__ == “__main__”
您很可能在许多 Python 程序中见过这种语法;当正在运行的 Python 文件是主程序时,Python 会使用一个特殊名称 "__main__" 并将其赋给标识符 __name__。
如果我们决定将截图中的模块(Python 文件)导入到另一个模块中并运行该文件,那么代码中表达式的真假将为假。这是因为当我们从另一个模块导入时,__name__ 会被设为该模块(Python 文件)的名称。
#19 ‘setdefault’ 方法
您可能希望为字典中的多个键设置值,例如统计语料库中的词频。常见做法如下:
代码如下所示:
更简洁的做法是对字典对象使用 setdefault() 方法。
传给该方法的第一个参数是我们要检查的键。第二个参数是在键不存在时应设置的值——如果键已存在,则方法会返回该键的值,从而不会被修改。
程序结构技巧
#20 正则匹配
正则表达式允许您指定要搜索的文本模式;大多数人都知道可以使用 Ctrl+F(Windows)来搜索,但如果您并不清楚要找的精确内容,该如何找到?答案是搜索“模式”。
例如,英国号码通常遵循相似的模式:要么以 0 开头后接十个数字,要么用 +44 代替开头的 0 后接十个数字——第二种是国际格式。
正则表达式能大幅节省时间。如果不用正则而是编写规则来捕获示例中的情况,可能需要 10 多行代码。
即使您不写代码,了解正则表达式的工作原理也很重要。大多数现代文本编辑器和文字处理器的查找替换功能都允许使用正则表达式。
#21 正则中的管道符
正则表达式有一个特殊字符称为管道符(|),允许您在多个表达式中匹配其一,且可以在任意位置使用。当您有多个相似的模式时,这非常方便。
例如,“Superman”“Superwoman”“Superhuman”都有相同前缀。因此您可以利用管道符保留模式中重复的部分,替换需要不同的部分,再次为您节省宝贵时间。
注意坑点:如果您要匹配的所有表达式都出现在同一文本中,将返回第一个匹配出现的位置——例如“An example text containing Superwoman, Superman, Superhuman”会返回 Superwoman。
#22 print() 函数的 ‘sep’ 参数
不知道 print() 函数全部能力的 Python 程序员数量之多,着实让人吃惊;如果“Hello World”是您的第一个程序,那么 print() 可能是您学习 Python 时最先接触的内置函数之一。我们用 print() 在屏幕上显示格式化信息,但它的功能远不止于此。
在上面的代码中,我们展示了几种不同的格式化输出方式。sep 参数是 print() 函数的可选参数,用于指定当包含多个对象时,它们之间应如何分隔。
默认是用空格分隔,但我们在打印语句中更改了这一行为——一个将 sep 设为 "",另一个将其设为 "."。
#23 Lambda 函数
Lambda 函数带您进入可以用 Python 完成的中高级技巧——通过本课程学习中级 Python。它们乍看复杂,其实很简单。
在示例代码中我们只用到了一个参数,但如有需要也可以使用多个参数:
本质上,lambda 关键字允许我们在一行内创建小型、受限、匿名的函数。它们的行为与用 def 声明的常规函数相同,只不过这类函数没有名字。
#24 ‘swapcase’ 方法
swapcase() 方法用于对字符串对象进行大小写互换:将大写变小写、将小写变大写,而且只需一行代码。此方法的用例不多,但知道也无妨。
#25 ‘isalnum’ 方法
假设我们要编写一个需要用户输入密码的程序,并要求密码必须由数字和字母组合而成。我们可以在一行代码中通过调用字符串实例的 isalnum() 来实现。
该方法会检查所有字符是否均为字母(A-Za-z)和数字(0-9)。包含空格或符号(!#%$&? 等)会返回 False。
#26 异常处理
Python 程序在遇到错误时会终止。
有时我们并不希望出现这种行为,比如当终端用户与我们的代码进行交互时。如果在这种情况下程序过早终止,会有多糟?
处理异常情况有几种思想流派。多数 Python 程序员通常拥抱“先斩后奏比事先请示更容易”的理念。这意味着他们更倾向于通过提供能够处理异常的上下文来捕获已引发的错误。这一思想背后的理由是:没必要把时间浪费在为各种可能的异常情况设置防护上。
但这只在问题发生后确有应对机制时才成立。
#27 识别列表差异
这里提供三种在 Python 中比较两个列表差异的方法。
注意:除非您确定 list_1 是 list_2 的子集,否则解法 1 与后两种解法并不相同。
#28 Args 与 kwargs
当我们不确定函数应接收多少参数时,会将 *args 与 **kwargs 作为函数参数。
*args 允许在参数为非关键字(即不需要关联名称)时,将可变数量的参数传入函数。相对地,**kwargs 允许将任意数量的关键字参数传入函数。
实际上,*args 与 **kwargs 这两个词并不神奇:真正的“魔法”在星号(*)上。这意味着星号后的词可以任意取名,但使用 args 与 kwargs 是常见惯例,并已在 Python 开发者中约定俗成。
#29 省略号
省略号(Ellipsis)是一个 Python 对象,可以通过连续三个点(...)或直接调用对象(Ellipsis)来表示。
它最著名的用途是在 NumPy 中访问和切片多维数组,例如:
省略号的另一个用法是在未实现的函数中充当占位符。
这意味着您可以使用 Ellipsis、... 或 pass,它们都同样有效。
#30 列表推导式
最后一个 Python 技巧是列表推导式,它是一种从其他序列优雅构建列表的方式。正如上面的代码所示,它允许您执行复杂的逻辑与过滤。
还有其他方法可以实现相同目标;例如,我们可以像下面这样使用 lambda 函数:
但许多 Pythonista 会认为这种方案相比列表推导式可读性差得多。