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