ITPub博客

首页 > 数据库 > 数据库开发技术 > Python编程技巧:在函数参数中请停止使用“x=[]”

Python编程技巧:在函数参数中请停止使用“x=[]”

原创 数据库开发技术 作者:jackzhang 时间:2024-08-10 17:23:29 0 删除 编辑

今天笔者将向大家分享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:,如果是则将其赋值给 []。

通过这种方式,就不会像使用可变默认参数那样产生奇怪的副作用(不期望的结果)。虽然这样使得代码变得更长,但为了确保逻辑的正确性,这是必须要做的事。

7. 结论

今天的分享到此结束,感谢你的阅读,希望这些浅显易懂的内容对你有所帮助!


推荐阅读👇👇👇

9个令人相见恨晚的Python字典技巧,让你写出更优雅、更Pythonic的代码!

Python设计模式(三):建造者模式(Builder Pattern)

Python设计模式(二):工厂模式(Factory Pattern)

每天都在用的20个Python技巧,让你从此告别平庸!

编写高效Python函数的5个技巧,真心建议你牢记,并当作此后编码的准绳!

- END -

🌟 如果你喜欢这篇文章并且愿意支持我的话,你可以:🌟

  • 👍👍👍 点赞文章
  • ⤴️⤴️⤴️ 转发文章
  • ⚛️⚛️⚛️ 点击在看
  • 🧑‍🤝‍🧑🧑‍🤝‍🧑🧑‍🤝‍🧑 让文章被推荐给更多人,找到更多志同道合的伙伴
  • 👏👏👏 欢迎给我留言

关注我,第一时间接收更多关于Python、数据分析和机器学习的深入分析和最新趋势。我们一起探索数据的力量,解锁AI的潜能。你的每一次互动都是对我的极大鼓励,也是我们共同成长的动力!

👇👇👇 关注并开启星标提醒,让知识与技能的火花不断在你的职业道路上燃烧吧!

原文作者:数据派探险家;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2017-12-19

  • 博文量
    119
  • 访问量
    55942