默认参数
参考:【Python】我精心设计的默认参数,怎么就出问题了呢?_哔哩哔哩_bilibili
在定义类的时候,会定义一些默认参数,可能会像这样
class Player:
def __init__(self, name, items=[]):
self.name = name
self.items = items
然后如果像这样调用的话
if __name__ == "__main__":
p1 = Player("John", ["sword", "shield"])
p2 = Player("Jane")
p3 = Player("Jack")
p2.items.append("bow")
p3.items.append("axe")
print(p3.items)
输出结果为['bow', 'axe']
。
很明显,这不应该是我们想要的结果,我们期望输出应该是['axe']
。
Python的Document里有这么一段话[1]
默认形参值会在执行函数定义时按从左至右的顺序被求值。 这意味着当函数被定义时将对表达式求值一次,相同的“预计算”值将在每次调用时被使用。 这一点在默认形参为可变对象,例如列表或字典的时候尤其需要重点理解:如果函数修改了该对象(例如向列表添加了一项),则实际上默认值也会被修改。 这通常不是人们所想要的。
简而言之,对于这种可变对象,如果使用默认参数,他们会指向同一个对象。
Document中也提到了解决方法
使用
None
作为默认值
因此,我们将类定义修改为如下,即可解决这一问题。
class Player:
def __init__(self, name, items=None):
if items is None:
items = []
self.name = name
self.items = items
使用is None对None进行判断
参考:【python】为什么判断一个值是否为None的时候,一定要用is呢?_哔哩哔哩_bilibili
不要直接作为布尔值判断(if None)
除了None之外,其他传入的空数据结构,如Dictionary,List,Tuple等也会被识别成False。
对于以下这种情况,我们原本期望共享ls1这个数组,最终能够输出[1, 1]
,可是程序运行最终只输出了[1]
,原因就是输入的ls1是空数组,使用逻辑判断结果为False。
class Test:
def __init__(self, ls=None):
if ls:
self.ls = ls
else:
self.ls = []
def cal(self):
self.ls.append(1)
def __str__(self):
return str(self.ls)
if __name__ == "__main__":
ls1 = []
t1 = Test(ls1)
t2 = Test(ls1)
t1.cal()
t2.cal()
print(t2)
我们也可以使用__bool__
自定义类判断为False
的情况。输出结果为output2。
class Test:
def __bool__(self):
return False
if Test():
print("output1")
else:
print("output2")
因此,由于一些数据结构会对bool进行重载,造成对None的判断出现问题。
也不要使用==
由于可以使用__eq__
对运算符==进行重载,也可能导致判断出错。以下代码结果即可输出output1。
class Test:
def __eq__(self, other):
return True
if Test() == None:
print("output1")
else:
print("output2")
使用is
某些运算符不能重载 —— is、and、or以及not(位运算符&、|以及~可以重载)[2]
使用dis这个module对==
和is
进行比较,观察字节码
Python3.11中运行结果
>>> import dis
>>> dis.dis('a == None')
0 0 RESUME 0
1 2 LOAD_NAME 0 (a)
4 LOAD_CONST 0 (None)
6 COMPARE_OP 2 (==)
12 RETURN_VALUE
>>> dis.dis('a is None')
0 0 RESUME 0
1 2 LOAD_NAME 0 (a)
4 LOAD_CONST 0 (None)
6 IS_OP 0
8 RETURN_VALUE
>>>
使用Python3.9
1 0 LOAD_NAME 0 (a)
2 LOAD_CONST 0 (None)
4 COMPARE_OP 2 (==)
6 RETURN_VALUE
None
1 0 LOAD_NAME 0 (a)
2 LOAD_CONST 0 (None)
4 IS_OP 0
6 RETURN_VALUE
None
可以看到只有COMPARE_OP
和IS_OP
的差别。
在Python源代码中,is是进行地址的指针比较,在c中是非常快的,而COMPARE_OP
则要慢。
generator中的坑
参考:【python】几乎没人做对的题目?聊聊生成器表达式里不为人知的秘密_哔哩哔哩_bilibili
对于以下的代码,运行结果为[1, 2]。
注意:第二行是(),不是生成式的[]。
ls = [1, 2, 3, 4, 5]
g = (n for n in ls if n in ls)
ls = [0, 1, 2]
print(list(g))
实际上,这段代码等价于
ls = [1, 2, 3, 4, 5]
g = (n for n in ls if n in ls2)
ls2 = [0, 1, 2]
print(list(g))
在PEP 289[3]中提到
Only the outermost for-expression is evaluated immediately, the other expressions are deferred until the generator is run
只有最外层的 for 表达式被立即计算,其他表达式被延迟到生成器运行