51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

查看: 876|回复: 0
打印 上一主题 下一主题

[python] 浅谈Python函数的默认值可变对象

[复制链接]
  • TA的每日心情
    无聊
    昨天 09:11
  • 签到天数: 936 天

    连续签到: 3 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2022-11-16 10:22:43 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    看到了有给 Python 函数参数的默认值传递可变对象,以此来加快斐波那契函数的递归速度,代码如下:
    1. def fib(n, cache={0: 0, 1: 1}):
    2.       if n not in cache:
    3.           cache[n] = fib(n - 1) + fib(n - 2)
    4.       return cache[n]
    复制代码


    是不是很新奇,居然可以这样,速度真的非常快,运行结果如下:




    不过,我劝你不要这样做,而且 IDE 也会提示你这样做很不好:




    这是因为,万物皆对象,Python 函数也是对象,参数的默认值就是对象的属性,在编译阶段参数的默认值就已经绑定到该函数,如果是可变对象,Python 函数参数的默认值在会被存储,并被所有的调用者共享,也就是说,一个函数的参数默认值如果是一个可变对象,例如 List、Dict,调用者 A 修改了它,那么之后调用者 B 在调用的时候看到的就是 A 修改后的结果,这样的模式往往会产生意想不到的结果,比如上面 fib 的算法,但更多的是 bug。
      可以看下这段简单的代码:

    1. def func(n, li = []):
    2.       for i in range(n):
    3.           li.append(i)
    4.       print(l)
    5.   func(2) # [0,1]
    6.   func(3,l=[1,2]) # [1,2,0,1,2]
    7.   func(2) # [0,1]
    复制代码
    你可以先估算一下这段代码的输出,如果和注释中的一样,那你就错了。正确的结果是:

    1.  [0, 1]
    2.   [1, 2, 0, 1, 2]
    3.   [0, 1, 0, 1]
    复制代码

    你可能会觉得,最后一个 func(2) 怎么是这样,不急,我们 print(id(li)) 调试一下:
    1.  def func(n, li = []):
    2.       print(id(li))
    3.       for i in range(n):
    4.           li.append(i)
    5.       print(li)
    6.   func(2)
    7.   func(3,li=[1,2])
    8.   func(2)
    复制代码
    结果如下:

    1.  140670243756736
    2.   [0, 1]
    3.   140670265684928
    4.   [1, 2, 0, 1, 2]
    5.   140670243756736
    6.   [0, 1, 0, 1]
    复制代码
     有没有发现,第一个 func(2) 和第二个 func(2) 的 id 是一样的,说明它们用到的是 li 是同一个,这就参数的默认值是可变对象的逻辑,对于所有的调用者来讲,是共享的。
      如果要深入研究 Python 为什么这么设计,可以移步 http://cenalulu.github.io/python/default-mutable-arguments/
      如何避免?
      最好的方式是不要使用可变对象作为函数默认值。如果非要这么用的话,下面是一种解决方案:
    1. def generate_new_list_with(my_list=None, element=None):
    2.       if my_list is None:
    3.           my_list = []
    4.       my_list.append(element)
    5.       return my_list
    复制代码
    这样,如果 my_list 默认值永远都是 []。
      最后
      我想那个 fib 函数的实现可能会让你印象深刻,不过请注意,这样的用法非常危险,不可用于自己的代码中。


    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-4-25 01:55 , Processed in 0.063044 second(s), 23 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表