新手请教关于 Python 函数参数默认值设计的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
palmers
V2EX    Python

新手请教关于 Python 函数参数默认值设计的问题

  •  
  •   palmers 2019 年 6 月 16 日 3547 次点击
    这是一个创建于 2437 天前的主题,其中的信息可能已经有所发展或是发生改变。

    举栗子:

    def gen_list_with(elements = [], e=None): elements.append(e) return elements rs = gen_list_with( e = 'world') print(rs) rs = gen_list_with(e = 'python') print(rs) 
    //输出 ['world'] ['world', 'python'] 

    我疑惑的是:
    1. 方法或函数的形参都是局部的,随着执行完毕,出栈后对应的执行环境都会被销毁,为什么还会出现这种情况呢?
    2. 这种情况在给 elements 指定值的情况下会消除, 为什么呢? 比如:

    rs = gen_list_with(elements = ['init'], e = 'world') rs = gen_list_with(e = 'python') print(rs) //输出 ['python'] 

    我只知道是因为函数形参使用了可变对象的原因, 但是为什么这么设计, 暂时还没有找到比较权威的说明,麻烦大家给解答一下, 或者给我一份官方或 python 作者这么设计的原因说明文档, 谢谢了

    21 条回复    2019-06-18 14:47:44 +08:00
    makdon
        1
    makdon  
       2019 年 6 月 16 日
    默认参数只初始化一次
    mooncakejs
        2
    mooncakejs  
       2019 年 6 月 16 日 via iPhone
    Python 的大坑。 就算怎么解释都是大坑。
    palmers
        3
    palmers  
    OP
       2019 年 6 月 16 日
    @makdon 您能说的详细一点 我再 python 官方文档上也看到了您说的这句话, 但是没有很详细的说明
    yxcxx
        4
    yxcxx  
       2019 年 6 月 17 日
    ```python
    def gen_list_with(elements = [], e=None):
    elements.append(e)
    print(id(elements))
    return elements

    rs = gen_list_with( e = 'world')

    print(rs)

    rs = gen_list_with(e = 'python')
    print(rs)
    ```

    140020230277000
    ['world']
    140020230277000
    ['world', 'python']
    makdon
        5
    makdon  
       2019 年 6 月 17 日
    官方的话,我印象中 Guido van rossum 似乎在博客还是采访中提到过这个的设计,但是我刚刚找了一圈没找到,也可能是记错了。
    可以参考一下[这个讨论]( https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument)
    还有这个“您”字我受不起受不起
    so1n
        6
    so1n  
       2019 年 6 月 17 日 via Android
    引用内存地址不变,你可以 print gen_list_with.__defaults__,里面就是你的参数了,
    andylsr
        7
    andylsr  
       2019 年 6 月 17 日 via Android
    这里传入的是变量的引用,而不是副本,两次 elements 其实使用的是同一个对象
    palmers
        8
    palmers  
    OP
       2019 年 6 月 17 日
    @makdon 好的 谢谢你了 我在一篇博客上也见到说在 stackoverflow 有这方面的讨论但是 也都是争论
    palmers
        9
    palmers  
    OP
       2019 年 6 月 17 日
    @so1n @andylsr 我主要是不太明白 这种设计命名有缺陷为什么还要这么设计,c java c++ 等 都是方法出栈都会销毁执行环境 我记忆中从不会有这种特性存在的
    palmers
        10
    palmers  
    OP
       2019 年 6 月 17 日
    @makdon 你找这个连接挺好的 谢谢了
    HelloAmadeus
        12
    HelloAmadeus  
       2019 年 6 月 17 日 via iPhone
    你把默认参数变量考虑成为用 static 修饰的变量可能更好理解一点
    lowman
        13
    lowman  
       2019 年 6 月 17 日
    如果从 C 去理解, 这些数据应该保存在静态存储区里, 而函数的局部变量保存在动态储存区里. 函数初始化的时候应该已经为这个变量分配了内存, 而且不会随着函数执行的结束而销毁. 如果从这点来看, 如果在程序的函数中过多得使用命名参数, 会占用更多的内存. 不知道是不是这样........
    fourstring
        14
    fourstring  
       2019 年 6 月 17 日
    “方法或函数的形参都是局部的,随着执行完毕,出栈后对应的执行环境都会被销毁,为什么还会出现这种情况呢?”这句话是从 C/C++的设计来理解的。Python 里会有这种问题是因为 Python 中函数是所谓的一类对象,你可以就把它当成函数类的一个对象,而所谓的函数类,也没有什么特别的,就是定义了几个特殊方法如__call__等。这样就很好理解,因为定义函数时的签名列表是这个对象中的实例变量,只要这个函数对象没有被销毁,其实例变量自然也不会被销毁。
    fourstring
        15
    fourstring  
       2019 年 6 月 17 日
    另外再说两点。第一,这样设计有没有好处?当然有,而且还很大。函数作为对象而非 C/C++中指向特定内存地址的代码在编程中有很实际的意义。函数作为对象直接让函数式编程成为了可能,因为后者的一大基础就是所谓的高阶函数。此外,即使不使用函数式编程的范式,装饰器这样的特性应该是每个 Python 程序员都会用到的,而函数作为对象正是装饰器之所以能存在之原因。
    第二,对 Python 中对象的行为不理解的话,可以阅读 Python Language Reference 中的 Data Model 一章。这一章除了是参考文档之外,更是一份对 Python 的哲学的解读。对 Python 的语言设计本身有看法的话,应该在先读过这一章之后才能评价自己的看法是否有道理可言。
    fourstring
        16
    fourstring  
       2019 年 6 月 17 日
    虽然 Python 的标准实现是 CPython,有些特别的问题也涉及到解释器本身的代码和优化,但是从理念上来说,不应该把 Python 看成一种快速写 C 代码的工具,也不应该用 C/C++的观念来看待 Python。Python 的哲学很多地方有其特质,我觉得这某种程度上也是它受欢迎的原因之一吧。
    palmers
        17
    palmers  
    OP
       2019 年 6 月 17 日
    @fourstring 谢谢你的耐心解答, 我之前使用最多的是 java 和 js 系语言,所以本能的从这些语言特性来学习 python 了 再结合 @makdon 我基本能理解 在 python 中 函数作为一类对象存在, 在上面的文档中也能体会到这么设计的好处, 但是我还是有很多疑问,比如,因为这种设计带来的副作用(缓存了上一次调用)为什么一直没有消除呢? 由于我现在还是一个很新的新手很多概念非常的不清楚 我估计继续讨论也没有太大价值, 就不讨论了 后面深入学习后如果还不理解 我再上 V2EX 请教你们 谢谢了
    kaneg
        18
    kaneg  
       2019 年 6 月 17 日 via iPhone
    默认参数应该是不可变的,否则是累加的,空数组这个坑很多人都踩过,正确做法是用 None
    siteshen
        19
    siteshen  
       2019 年 6 月 18 日
    # 因为表达式 `[]` 是在编译期执行的,函数得到的是表达式的值 `[]` (空数组),而不是表达式 `[]`。因为
    # 空数组的表达式和值同型,可能容易忽略值和表达式的区别,但下面这个例子,应该能说明函数定义时得到的
    # 是值,而不是表达式。
    #
    # 如果不这么设计会怎么样?函数需要保存表达式及上下文,并且在调用时执行表达式,会……很复杂。

    from datetime import datetime


    def print_time(time=datetime.now()):
    print('time is', time)


    print_time()
    print_time()
    siteshen
        20
    siteshen  
       2019 年 6 月 18 日
    @siteshen #19 另外建议直接写无副作用的代码,根本不给「副作用」坑你的机会。
    annoymous
        21
    annoymous  
       2019 年 6 月 18 日
    分不清楚的话 可以遵照上面的写法 永远返回一个 copy 保证安全
    关于     帮助文档     自助推广系统     < href="https://blog.v2ex.com/" class="dark" target="_blank">博客     API     FAQ     Solana     1855 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 09:40 PVG 17:40 LAX 01:40 JFK 04:40
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86