今天向大家分享Python函数中应该避免的一个小细节。
为了实现代码的重用,避免重复造轮子,通常我们会将一段常用的代码逻辑封装为函数,这样就可以实现代码的一处编写,多处调用。
在函数设计和编写中,常常会用到默认值参数,即在函数定义的时候就给定默认值。调用函数的时候,如果不给默认值参数传递值,则它将使用函数定义时设定的默认值。
本文要提醒大家的是,参数的默认值最好不要为可变类型,比如,x=[]
。虽然这在Python的语法上是合法的,但合法的东西并不一定是好东西。比如,生活中你无故对陌生人作出无礼的行为,虽然不违法,但可能带来意想不到的后果。
def my_func(lst: List[str] = []): # do something
在程序的世界中也是类似的道理。在Python函数中可变默认参数是_合法的_——我们可以运行上面这样的代码,并且Python也是允许的。然而,这并不是一个好的实践,也并不推荐这样做。
1. 可变性和不可变性的含义
可变性(Mutability):
如果一个数据结构在创建后可以修改,那么它就是可变的。在Python中,像列表(
List
)、字典(Dict
)和集合(Set
)这样的数据类型都是可变数据结构。
不可变性(Immutability):
与可变性相反,如果一个数据结构在创建之后不可更改,那么它就是不可变的。在Python中,像整数、浮点数、布尔型、字符串、None、元组(
tuple
)和冻结集合(fozensets
)这样的数据类型都是不可变的。
2. 为什么要使用默认参数?
def say_hello(obj: str, greeting: str='Hello') -> None: print(f'{greeting}, {obj}!') if __name__ == '__main__': say_hello(obj='World') # Hello, World! say_hello(obj='World', greeting='Honey') # Honey, World!
在上面的函数中,greeting
就是一个默认参数。在调用函数时,如果我们不给 greeting
传值,那么它将采用默认值 Hello
。如果我们给它传递了值,那它就会接受我们传递的值。
因此,如果我们可以接受默认参数的默认值,在函数调用时就可以选择不传递默认参数,例如将上面示例中的 greeting
参数值设为 Hello
。当函数中某个参数的值多数情况下不变时,就可以采用默认参数,比如一个读取文件的函数,如果文件路径一般不会改变,那就可以将其设置为默认参数。
3. 为什么不推荐使用可变默认参数呢?
def my_func(fruits: List[str] = []): fruits.append('apple') return fruits
这里,我们有一个接受 fruits
作为参数的函数 my_func
,该函数会将 apple
追加到列表中,然后返回列表。
-
fruits
是一个默认参数。 -
如果我们给
fruits
传递东西,它就会接受该值。 -
如果我们不
fruits
传递东西,它就会使用默认值[]
。
a = my_func(['banana']) print(a) # ['banana', 'apple']
这里,我们给 fruits
传递了内容,调用函数时,它将取值 banana
,然后返回 ['banana', 'apple']
。
a = my_func() print(a) # ['apple']
如果我们不向 fruits
传递任何内容,那么 fruits
将使用默认值 []
,函数将返回 ['apple']
。
4. 那么问题来了
print(my_func()) # ['apple'] print(my_func()) # ['apple', 'apple'] print(my_func()) # ['apple', 'apple', 'apple']
如果我们在不给 fruits
传递任何内容的前提下,多次调用函数,就会发生很奇怪的事。
-
第一次调用
my_func()
,fruits
被赋值给[]
,而函数体中的fruits.append('apple')
则会使它变成['apple']
。 -
第二次调用
my_func()
,此时fruits
的值为['apple']
。我们再次执行fruits.append('apple')
,fruits
的值就变成了['apple', 'apple']
。 -
第三次调用
my_func()
,此时fruits
的值为['apple', 'apple']
。再次执行fruits.append('apple')
后,fruits
的值就变成了['apple', 'apple', 'apple']
。
5. 为什么会发生这种情况呢?
from typing import List def my_func(fruits: List[str] = []) -> List[str]: fruits.append('apple') return fruits if __name__ == '__main__': print(my_func()) # ['apple'] print(my_func()) # ['apple', 'apple'] print(my_func()) # ['apple', 'apple', 'apple']
原因是:当我们定义函数 my_func()
时,Python解释器只会读取 fruits: List[str] = []
一次。
如果我们执行 fruits.append('apple')
或其他行为,对 fruits
的这一改变将会保留在函数中,因为 fruits
不会再被赋值给 []
。
6. 那么应该如何避免这种情况呢?
只需要将 fruits: List[str] = []
的默认值修改为 None
(不可变数据类型),并且在函数体中对 fruits
做一个是否为 None
的判断即可。
from typing import List def my_func(fruits: List[str] = None) -> List[str]: if fruits is None: fruits = [] fruits.append('apple') return fruits if __name__ == '__main__': print(my_func()) # ['apple'] print(my_func()) # ['apple'] print(my_func()) # ['apple']
-
在函数定义中,我们将默认参数
fruits
的默认值设为不可变值None
。 -
然后,我们判断
fruits
是否为None
, 即if fruits is None:
,如果是则将其赋值给[]
。
通过这种方式,就不会像使用可变默认参数那样产生奇怪的副作用(不期望的结果)。虽然这样使得代码变得更长,但为了确保逻辑的正确性,这是必须要做的事。
关于Python技术储备
学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!
👉Python学习路线汇总👈
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
👉Python必备开发工具👈
👉Python学习视频合集👈
观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
👉实战案例👈
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
👉Python副业兼职路线&方法👈
学好 Python 不论是就业还是做副业赚钱都不错,但要学会兼职接单还是要有一个学习规划。
👉 这份完整版的Python全套学习资料已经上传,朋友们如果需要可以扫描下方二维码免费领取