问:
任何修补 Python 时间足够长的人都被以下问题咬伤(或撕成碎片):
def foo(a=[]):
a.append(5)
return a
Python 新手会期望这个不带参数调用的函数总是返回一个只有一个元素的列表:[5]。结果却大不相同,而且非常惊人(对于新手来说):
>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()
我的一位经理曾经第一次遇到此功能,并称其为该语言的“戏剧性设计缺陷”。我回答说这种行为有一个潜在的解释,如果你不了解内部情况,确实非常令人费解和意外。但是,我无法(对自己)回答以下问题:在函数定义而不是函数执行时绑定默认参数的原因是什么?我怀疑经验丰富的行为是否有实际用途(谁真正在 C 中使用静态变量,没有滋生错误?)
编辑:
Baczek made an interesting example。连同您的大部分评论和Utaal’s in particular,我进一步阐述:
>>> def a():
... print("a executed")
... return []
...
>>>
>>> def b(x=a()):
... x.append(5)
... print(x)
...
a executed
>>> b()
[5]
>>> b()
[5, 5]
对我来说,设计决策似乎与将参数范围放在哪里有关:在函数内部,还是与它“一起”?
在函数内部进行绑定意味着 x 在函数被调用时有效地绑定到指定的默认值,而不是定义,这会带来一个严重的缺陷:def 行将是“混合”的,即(函数对象的)绑定的一部分将在定义时发生,而部分(默认参数的分配)将在函数调用时发生。
实际行为更加一致:该行的所有内容都在执行该行时进行评估,这意味着在函数定义处。
答1:
huntsbot.com聚合了超过10+全球外包任务平台的外包需求,寻找外包任务与机会变的简单与高效。
实际上,这不是设计缺陷,也不是因为内部或性能。这仅仅是因为 Python 中的函数是一流的对象,而不仅仅是一段代码。
只要你这样想,那么它就完全有道理了:函数是根据其定义进行评估的对象;默认参数是一种“成员数据”,因此它们的状态可能会从一个调用更改为另一个调用 - 就像在任何其他对象中一样。
无论如何,effbot (Fredrik Lundh) 对 Default Parameter Values in Python 中这种行为的原因有一个很好的解释。我发现它非常清楚,我真的建议阅读它以更好地了解函数对象的工作原理。
对于阅读上述答案的任何人,我强烈建议您花时间阅读链接的 Effbot 文章。除了所有其他有用的信息外,关于如何将此语言功能用于结果缓存/记忆的部分非常容易了解!
即使它是一流的对象,人们仍然可以设想这样一种设计,其中每个默认值的代码与对象一起存储并在每次调用函数时重新评估。我并不是说这会更好,只是函数是一流的对象并不能完全排除它。
抱歉,任何被认为是“Python 中最大的 WTF”的东西绝对是一个设计缺陷。在某些时候,这对每个人来说都是错误的来源,因为没有人在一开始就期望这种行为 - 这意味着它不应该从一开始就被设计成那样。我不在乎他们必须跳过什么箍,他们应该设计 Python,以便默认参数是非静态的。
无论这是否是设计缺陷,您的回答似乎暗示这种行为在某种程度上是必要的、自然和明显的,因为函数是一流的对象,而事实并非如此。 Python 有闭包。如果将默认参数替换为函数第一行的赋值,它会在每次调用时评估表达式(可能使用在封闭范围中声明的名称)。完全没有理由每次以完全相同的方式调用函数时都评估默认参数是不可能或不合理的。
该设计并非直接沿用 functions are objects。在您的范例中,建议是将函数的默认值作为属性而不是属性来实现。
答2:
huntsbot.com洞察每一个产品背后的需求与收益,从而捕获灵感
假设您有以下代码
fruits = ("apples", "bananas", "loganberries")
def eat(food=fruits):
...
当我看到eat的声明时,最不奇怪的是认为如果第一个参数没有给出,它会等于元组(“apples”, “bananas”, “loganberries”)
但是,假设稍后在代码中,我会执行类似的操作
def some_random_function():
global fruits
fruits = ("blueberries", "mangos")
那么如果默认参数是在函数执行而不是函数声明时绑定的,我会惊讶地(以一种非常糟糕的方式)发现水果已经改变了。这比发现上面的 foo 函数正在改变列表更令人惊讶。
真正的问题在于可变变量,所有语言都在一定程度上存在这个问题。这是一个问题:假设在 Java 中我有以下代码:
StringBuffer s = new StringBuffer("Hello World!");
Map counts = new HashMap();
counts.put(s, 5);
s.append("!!!!");
System.out.println( counts.get(s) ); // does this work?
现在,我的地图在放入地图时是使用 StringBuffer 键的值,还是通过引用存储该键?无论哪种方式,都会有人感到惊讶。尝试使用与放入对象的值相同的值从 Map 中取出对象的人,或者即使他们使用的键字面上也无法检索对象的人用于将其放入地图的同一对象(这实际上是 Python 不允许将其可变内置数据类型用作字典键的原因)。
您的示例是 Python 新手会感到惊讶和被咬的一个很好的例子。但我认为,如果我们“解决”这个问题,那只会造成一种不同的情况,他们会被咬伤,而且这种情况会更不直观。此外,在处理可变变量时总是如此;您总是会遇到这样的情况,即有人可以根据他们正在编写的代码直观地期待一种或相反的行为。
我个人喜欢 Python 当前的方法:在定义函数时评估默认函数参数,并且该对象始终是默认值。我想他们可以使用空列表进行特殊处理,但这种特殊的大小写会引起更多的惊讶,更不用说向后不兼容了。
我认为这是一个有争议的问题。您正在对全局变量进行操作。在代码中任何地方执行的涉及全局变量的任何评估现在都将(正确地)引用(“blueberries”,“mangos”)。默认参数可能与任何其他情况一样。
实际上,我认为我不同意您的第一个示例。我不确定我是否喜欢首先像这样修改初始化程序的想法,但如果我这样做了,我希望它的行为与您描述的完全一样——将默认值更改为 ("blueberries", "mangos")。
默认参数与任何其他情况一样。出乎意料的是该参数是全局变量,而不是局部变量。这又是因为代码在函数定义处执行,而不是调用。一旦你明白了,对于课程也是如此,那就非常清楚了。
我发现这个例子具有误导性而不是出色。如果 some_random_function() 附加到 fruits 而不是分配给它,eat() 的行为 将 改变。就目前的精彩设计而言。如果您使用在其他地方引用的默认参数,然后从函数外部修改引用,您就是在自找麻烦。真正的 WTF 是当人们定义一个新的默认参数(列表文字或对构造函数的调用),并且 still 得到一点。
您只是显式声明了 global 并重新分配了元组 - 如果 eat 之后的工作方式不同,这绝对没有什么令人惊讶的。
答3:
huntsbot.com精选全球7大洲远程工作机会,涵盖各领域,帮助想要远程工作的数字游民们能更精准、更高效的找到对方。
documentation 的相关部分:
执行函数定义时,从左到右计算默认参数值。这意味着表达式在定义函数时被计算一次,并且每次调用都使用相同的“预计算”值。当默认参数是可变对象(例如列表或字典)时,这一点尤其重要:如果函数修改了对象(例如,通过将项目附加到列表中),则默认值实际上已被修改。这通常不是预期的。解决这个问题的一种方法是使用 None 作为默认值,并在函数体中显式测试它,例如: def whats_on_the_telly(penguin=None): if penguin is None: penguin = [] penguin.append("property of动物园”)归来企鹅
“这通常不是预期的”和“解决这个问题的方法”这些短语闻起来像是在记录设计缺陷。
@bukzor:需要注意和记录陷阱,这就是为什么这个问题很好并且得到了如此多的支持。同时,陷阱不一定需要消除。有多少 Python 初学者将一个列表传递给一个修改它的函数,并且惊讶地看到原始变量中出现的变化?然而,当您了解如何使用可变对象类型时,它们非常棒。我想这归结为对这个特殊陷阱的看法。
短语“这通常不是预期的”意味着“不是程序员真正想要发生的事情”,而不是“不是 Python 应该做的事情”。
@holdenweb 哇,我迟到了。鉴于上下文,bukzor 是完全正确的:当他们决定语言应该执行函数的定义时,他们正在记录并非“有意”的行为/后果。由于这是他们设计选择的意外结果,因此这是一个设计缺陷。如果这不是设计缺陷,甚至没有必要提供“解决这个问题的方法”。
我们可以用它来聊天并讨论它还有什么可能,但语义已经被彻底辩论过,没有人能想出一个明智的机制来创建默认值-on-call。一个严重的问题是,调用范围通常与定义完全不同,如果在调用时评估默认值,则名称解析不确定。 “绕过”意味着“您可以通过以下方式实现您想要的目的”,而不是“这是 Python 设计中的错误”。
答4:
HuntsBot周刊–不定时分享成功产品案例,学习他们如何成功建立自己的副业–huntsbot.com
我对 Python 解释器的内部工作一无所知(而且我也不是编译器和解释器方面的专家),所以如果我提出任何不明智或不可能的建议,请不要怪我。
如果 python 对象是可变的,我认为在设计默认参数时应该考虑到这一点。当您实例化列表时:
a = []
您希望获得 a 引用的新列表。
为什么要在 a=[]
def x(a=[]):
在函数定义而不是调用时实例化一个新列表?就像您在问“如果用户不提供参数然后实例化一个新列表并使用它,就好像它是由调用者生成的一样”。我认为这是模棱两可的:
def x(a=datetime.datetime.now()):
用户,您希望 a 默认为您定义或执行 x 时对应的日期时间吗?在这种情况下,与前一个一样,我将保持相同的行为,就好像默认参数“赋值”是函数的第一条指令(在函数调用时调用 datetime.now())。另一方面,如果用户想要定义时间映射,他可以写:
b = datetime.datetime.now()
def x(a=b):
我知道,我知道:这是一个结束。或者,Python 可能会提供一个关键字来强制定义时绑定:
def x(static a=b):
你可以这样做: def x(a=None): 然后,如果 a 是 None,设置 a=datetime.datetime.now()
这次真是万分感谢。我真的无法解释为什么这让我感到无休止。你用最少的模糊和混乱做得很好。作为一个来自 C++ 系统编程并且有时天真地“翻译”语言特性的人,这个虚假的朋友让我大吃一惊,就像类属性一样。我明白事情为什么会这样,但我不禁不喜欢它,不管它会带来什么积极的影响。至少它与我的经验如此相反,我可能(希望)永远不会忘记它......
@Andreas 一旦你使用 Python 足够长的时间,你就会开始看到 Python 以它的方式将事物解释为类属性是多么合乎逻辑——这仅仅是因为 C++(和 Java 等语言)的特殊怪癖和限制,以及C#...) 将 class {} 块的内容解释为属于 instances 是有意义的 :) 但是当类是一等对象时,很自然的事情是它们的内容(在内存中)以反映它们的内容(在代码中)。
在我的书中,规范结构不是怪癖或限制。我知道它可能笨拙和丑陋,但你可以称它为某事物的“定义”。动态语言在我看来有点像无政府主义者:当然每个人都是自由的,但你需要结构来让某人清空垃圾并铺平道路。估计我老了……:)
函数定义在模块加载时执行。函数体在函数调用时执行。默认参数是函数定义的一部分,而不是函数体。 (嵌套函数变得更加复杂。)
答5:
huntsbot.com精选全球7大洲远程工作机会,涵盖各领域,帮助想要远程工作的数字游民们能更精准、更高效的找到对方。
嗯,原因很简单,绑定是在代码执行时完成的,并且函数定义也被执行,嗯……当函数被定义时。
比较一下:
class BananaBunch:
bananas = []
def addBanana(self, banana):
self.bananas.append(banana)
此代码遭受完全相同的意外事件。香蕉是一个类属性,因此,当您向其中添加内容时,它会添加到该类的所有实例中。原因完全一样。
这只是“它是如何工作的”,并且在函数案例中使其以不同的方式工作可能会很复杂,而在类案例中可能是不可能的,或者至少会大大减慢对象实例化的速度,因为您必须保留类代码并在创建对象时执行它。
是的,这是出乎意料的。但是一旦一分钱下降,它就完全符合 Python 的一般工作方式。事实上,它是一个很好的教具,一旦你理解了为什么会发生这种情况,你就会更好地了解 python。
也就是说,它应该在任何优秀的 Python 教程中占据显着位置。因为正如你所说,每个人迟早都会遇到这个问题。
您如何为类的每个实例定义不同的类属性?
如果每个实例都不同,则它不是类属性。类属性是 CLASS 上的属性。由此得名。因此,它们对于所有实例都是相同的。
你如何在一个类中定义一个类的每个实例都不同的属性? (为那些无法确定不熟悉 Python 命名约定的人可能会询问类的普通成员变量的人重新定义)。
@Kievieli:你在谈论一个类的普通成员变量。 :-) 您可以通过在任何方法中说 self.attribute = value 来定义实例属性。例如 __init__()。
@Kieveli:两个答案:您不能,因为您在类级别定义的任何东西都将是类属性,并且访问该属性的任何实例都将访问相同的类属性;你可以,/sort of/,通过使用 propertys - 这实际上是类级别的函数,它们的作用类似于普通属性,但将属性保存在实例而不是类中(通过使用 Lennart 所说的 self.attribute = value)。
答6:
huntsbot.com高效搞钱,一站式跟进超10+任务平台外包需求
为什么不反省?
我真的很惊讶没有人对可调用对象执行 Python(2 和 3 应用)提供的富有洞察力的内省。
给定一个简单的小函数 func,定义为:
>>> def func(a = []):
... a.append(5)
当 Python 遇到它时,它会做的第一件事就是编译它,以便为这个函数创建一个 code 对象。完成此编译步骤后,Python 评估*,然后存储默认参数(此处为空列表 [])在函数对象本身中。正如上面提到的答案:列表 a 现在可以被视为函数 func 的 成员。
所以,让我们做一些自省,检查列表是如何在函数对象内部扩展的。我为此使用 Python 3.x,对于 Python 2 同样适用(在 Python 2 中使用 defaults 或 func_defaults;是的,同一事物的两个名称)。
执行前的功能:
>>> def func(a = []):
... a.append(5)
...
在 Python 执行此定义后,它将采用指定的任何默认参数(此处为 a = [])和 cram them in the defaults attribute for the function object(相关部分:Callables):
>>> func.__defaults__
([],)
好的,正如预期的那样,一个空列表作为 defaults 中的单个条目。
执行后的功能:
现在让我们执行这个函数:
>>> func()
现在,让我们再看看那些 defaults:
>>> func.__defaults__
([5],)
惊讶? 对象内部的值发生了变化!对该函数的连续调用现在将简单地附加到嵌入的 list 对象:
>>> func(); func(); func()
>>> func.__defaults__
([5, 5, 5, 5],)
所以,你有它,这个“缺陷”发生的原因是因为默认参数是函数对象的一部分。这里没有什么奇怪的事情,只是有点令人惊讶。
解决这个问题的常用解决方案是使用 None 作为默认值,然后在函数体中初始化:
def func(a = None):
# or: a = [] if a is None else a
if a is None:
a = []
由于每次都重新执行函数体,如果没有为 a 传递参数,您总是会得到一个新的空列表。
要进一步验证 defaults 中的列表是否与函数 func 中使用的列表相同,您只需更改函数以返回函数体内使用的列表 a 的 id。然后,将它与 defaults 中的列表(defaults 中的位置 [0])进行比较,您将看到它们实际上是如何引用同一个列表实例的:
>>> def func(a = []):
... a.append(5)
... return id(a)
>>>
>>> id(func.__defaults__[0]) == func()
True
都具有内省的力量!
- 要验证 Python 在函数编译期间评估默认参数,请尝试执行以下命令:
def bar(a=input('Did you just see me without calling the function?')):
pass # use raw_input in Py2
您会注意到,在构建函数并将其绑定到名称 bar 之前调用了 input()。
最后一次验证是否需要 id(...),或者 is 运算符会回答相同的问题吗?
@das-g is 会很好,我只是使用 id(val) 因为我认为它可能更直观。
使用 None 作为默认值严重限制了 __defaults__ 内省的有用性,因此我认为这不能很好地保护 __defaults__ 以它的方式工作。惰性评估将做更多的事情来保持函数默认值对双方都有用。
答7:
huntsbot.com高效搞钱,一站式跟进超10+任务平台外包需求
我曾经认为在运行时创建对象会是更好的方法。我现在不太确定,因为您确实失去了一些有用的功能,尽管为了防止新手混淆,这可能是值得的。这样做的缺点是:
- 性能
def foo(arg=something_expensive_to_compute())):
...
如果使用调用时评估,那么每次使用函数时都会调用昂贵的函数而不带参数。您要么为每次调用付出昂贵的代价,要么需要在外部手动缓存值,从而污染您的命名空间并增加冗长性。
2.强制绑定参数
一个有用的技巧是在创建 lambda 时将 lambda 的参数绑定到变量的当前绑定。例如:
funcs = [ lambda i=i: i for i in range(10)]
这将返回一个函数列表,这些函数分别返回 0,1,2,3…。如果行为发生变化,它们会将 i 绑定到 i 的 call-time 值,因此您将获得所有返回 9 的函数列表。
否则,实现这一点的唯一方法是使用 i 绑定创建进一步的闭包,即:
def make_func(i): return lambda: i
funcs = [make_func(i) for i in range(10)]
- 内省
考虑代码:
def foo(a='test', b=100, c=[]):
print a,b,c
我们可以使用 inspect 模块获取有关参数和默认值的信息,该模块
>>> inspect.getargspec(foo)
(['a', 'b', 'c'], None, None, ('test', 100, []))
这些信息对于文档生成、元编程、装饰器等非常有用。
现在,假设可以更改默认值的行为,使其等效于:
_undefined = object() # sentinel value
def foo(a=_undefined, b=_undefined, c=_undefined)
if a is _undefined: a='test'
if b is _undefined: b=100
if c is _undefined: c=[]
然而,我们已经失去了反省的能力,看看默认参数是什么。因为对象还没有被构造,所以如果不实际调用函数,我们就永远无法获得它们。我们能做的最好的事情是存储源代码并将其作为字符串返回。
如果每个函数都有一个创建默认参数而不是值的函数,您也可以实现自省。检查模块只会调用该函数。
@SilentGhost:我说的是是否更改了行为以重新创建它 - 创建一次是当前行为,以及为什么存在可变默认问题。
@yairchu:假设构造是安全的(即没有副作用)。反省 args 不应该做任何事情,但评估任意代码很可能最终会产生影响。
不同的语言设计通常意味着以不同的方式编写东西。你的第一个例子可以很容易地写成: def foo(arg=_expensive),如果您特别不想重新评估它。
@Glenn - 这就是我所说的“在外部缓存变量” - 它有点冗长,但是你最终会在你的命名空间中得到额外的变量。
答8:
huntsbot.com全球7大洲远程工作机会,探索不一样的工作方式
5分防守Python
简单:行为在以下意义上很简单:大多数人只掉入这个陷阱一次,而不是几次。一致性:Python 总是传递对象,而不是名称。显然,默认参数是函数标题的一部分(不是函数体)。因此,它应该在模块加载时进行评估(并且仅在模块加载时,除非嵌套),而不是在函数调用时。有用性:正如 Frederik Lundh 在他对“Python 中的默认参数值”的解释中指出的那样,当前行为对于高级编程非常有用。 (谨慎使用。) 足够的文档:在最基本的 Python 文档中,本教程中,该问题在“更多关于定义函数”部分的第一小节中被大声宣布为“重要警告”。警告甚至使用粗体字,这很少应用在标题之外。 RTFM:阅读精美的手册。元学习:陷入陷阱实际上是一个非常有帮助的时刻(至少如果你是一个反思型学习者),因为你随后会更好地理解上面的“一致性”这一点,这将教会你很多关于 Python 的知识。
我花了一年的时间才发现这种行为会弄乱我的生产代码,最终删除了一个完整的功能,直到我偶然遇到了这个设计缺陷。我正在使用 Django。由于暂存环境没有很多请求,因此此错误从未对 QA 产生任何影响。当我们上线并同时收到许多请求时 - 一些实用程序函数开始覆盖彼此的参数!制造安全漏洞、错误等等。
@oriadam,无意冒犯,但我想知道您是如何学习 Python 而没有遇到过这个问题的。我现在正在学习 Python,这个可能的陷阱是 mentioned in the official Python tutorial 就在第一次提到默认参数的同时。 (如本答案的第 4 点所述。)我认为道德是阅读您用于创建生产软件的语言的官方文档,而不是同情。
此外,如果除了我正在进行的函数调用之外还调用了一个未知复杂性的函数,那将是令人惊讶的(对我来说)。
@oriadam,您的公司需要在开发、登台和生产环境时使用他们编写的语言进行代码审查和实际的专家编码人员。新手错误和不良代码习惯不应该出现在生产代码中
答9:
保持自己快人一步,享受全网独家提供的一站式外包任务、远程工作、创意产品订阅服务–huntsbot.com
这种行为很容易解释为:
函数(类等)声明只执行一次,创建所有默认值对象,一切都通过引用传递
所以:
def x(a=0, b=[], c=[], d=0):
a = a + 1
b = b + [1]
c.append(1)
print a, b, c
a 不变 - 每个赋值调用都会创建新的 int 对象 - 打印新对象 b 不变 - 从默认值构建新数组并打印 c 更改 - 对同一对象执行操作 - 并打印
(实际上,add 是一个不好的例子,但整数不可变仍然是我的主要观点。)
在检查后意识到这一点让我很懊恼,当 b 设置为 [] 时,b.__add__([1]) 返回 [1],但即使列表是可变的,b 仍然是 []。我的错。
@ANon:有 __iadd__,但它不适用于 int。当然。 :-)
答10:
huntsbot.com全球7大洲远程工作机会,探索不一样的工作方式
1)所谓的“可变默认参数”问题通常是一个特殊的例子,证明:“所有有这个问题的函数在实际参数上也存在类似的副作用问题”,这违反了函数式编程的规则,通常是不可取的,应将两者固定在一起。
例子:
def foo(a=[]): # the same problematic function
a.append(5)
return a
>>> somevar = [1, 2] # an example without a default parameter
>>> foo(somevar)
[1, 2, 5]
>>> somevar
[1, 2, 5] # usually expected [1, 2]
解决方案:复制绝对安全的解决方案是copy或deepcopy输入首先对象,然后对副本执行任何操作。
def foo(a=[]):
a = a[:] # a copy
a.append(5)
return a # or everything safe by one line: "return a + [5]"
许多内置的可变类型都有像 some_dict.copy() 或 some_set.copy() 这样的复制方法,或者可以像 somelist[:] 或 list(some_list) 这样轻松复制。每个对象也可以通过 copy.copy(any_object) 或更彻底地通过 copy.deepcopy() 复制(如果可变对象由可变对象组成,则后者很有用)。有些对象基本上是基于像“文件”对象这样的副作用,并且不能通过副本进行有意义的复制。 copying
a similar SO question 的示例问题
class Test(object): # the original problematic class
def __init__(self, var1=[]):
self._var1 = var1
somevar = [1, 2] # an example without a default parameter
t1 = Test(somevar)
t2 = Test(somevar)
t1._var1.append([1])
print somevar # [1, 2, [1]] but usually expected [1, 2]
print t2._var1 # [1, 2, [1]] but usually expected [1, 2]
它不应保存在此函数返回的实例的任何 public 属性中。 (假设实例的 private 属性不应按照约定从此类或子类外部修改。即 _var1 是私有属性)
结论: 输入参数对象不应就地修改(变异),也不应绑定到函数返回的对象中。 (如果我们更喜欢没有副作用的编程,强烈推荐。请参阅 Wiki about “side effect”(前两段与此上下文相关。)。)
-
仅当需要对实际参数产生副作用但对默认参数不需要时,有用的解决方案是 def …(var1=None): if var1 is None: var1 = [] More…
-
在某些情况下是the mutable behavior of default parameters useful。
我希望您知道 Python 不是一种函数式编程语言。
是的,Python 是一种具有一些功能特性的多范式语言。 (“不要仅仅因为你有一把锤子就让每个问题看起来都像钉子。”)其中许多都是 Python 的最佳实践。 Python 有一个有趣的 HOWTO Functional Programming 其他特性是闭包和柯里化,这里没有提到。
我还要补充一点,在这个后期阶段,Python 的赋值语义已被明确设计为在必要时避免数据复制,因此创建副本(尤其是深度副本)将对运行时和内存使用产生不利影响。因此,它们只应在必要时使用,但新手通常难以理解何时使用。
@holdenweb 我同意。临时副本是最常用的方法,有时也是保护原始可变数据免受可能修改它们的无关函数的唯一可能方法。幸运的是,一个不合理地修改数据的函数被认为是一个错误,因此并不常见。
我同意这个答案。而且我不明白为什么当您真正的意思是其他意思时建议使用 def f( a = None ) 构造。复制是可以的,因为你不应该改变论点。当您执行 if a is None: a = [1, 2, 3] 时,无论如何您都会复制该列表。
答11:
打造属于自己的副业,开启自由职业之旅,从huntsbot.com开始!
你要问的是为什么:
def func(a=[], b = 2):
pass
在内部不等价于:
def func(a=None, b = None):
a_default = lambda: []
b_default = lambda: 2
def actual_func(a=None, b=None):
if a is None: a = a_default()
if b is None: b = b_default()
return actual_func
func = func()
除了显式调用 func(None, None) 的情况,我们将忽略它。
换句话说,与其评估默认参数,为什么不存储它们中的每一个,并在调用函数时评估它们呢?
一个答案可能就在那里——它可以有效地将每个带有默认参数的函数变成一个闭包。即使它全部隐藏在解释器中而不是完全关闭,数据也必须存储在某个地方。它会更慢并使用更多内存。
它不需要是一个闭包——一种更好的思考方式是让字节码创建默认值作为第一行代码——毕竟你在那一点上编译了主体——代码之间没有真正的区别在正文中的参数和代码中。
是的,但它仍然会减慢 Python 的速度,而且实际上会非常令人惊讶,除非你对类定义做同样的事情,这会使它变得非常慢,因为你每次实例化一个类时都必须重新运行整个类定义班级。如前所述,修复将比问题更令人惊讶。
同意伦纳特。正如 Guido 喜欢说的那样,对于每一种语言特性或标准库,都有人在使用它。
现在改变它会很疯狂——我们只是在探索它为什么会这样。如果它一开始就进行了后期默认评估,那并不一定令人惊讶。毫无疑问,这样一个核心的解析差异会对整个语言产生广泛的影响,而且可能会产生许多模糊的影响。
huntsbot.com – 高效赚钱,自由工作

479

被折叠的 条评论
为什么被折叠?



