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