以下问题被咬伤了(或撕成零件)的任何人都被咬伤了足够长的时间:
def foo(a=[]):
a.append(5)
return a
Python Nevices会期望此功能无参数始终只有一个元素返回列表: [5]
> 。相反,结果是非常不同的,而且非常令人惊讶(对于新手):
>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()
我的经理曾经第一次与此功能相遇,并称其为语言的“戏剧性设计缺陷”。我回答说,这种行为具有基本的解释,如果您不了解内部元素,这确实非常令人困惑和出乎意料。但是,我无法(我自己)回答以下问题:在函数定义上绑定默认参数的原因是什么,而不是在函数执行时绑定什么? 谁真正使用了C中的静态变量,而没有繁殖错误?
我怀疑经验丰富的行为是否有
实际用途( “> Baczek做了一个有趣的例子。与您的大多数评论一起, utaal>尤其是,我进一步阐述了:
def a():
print("a executed")
return []
def b(x=a()):
x.append(5)
print(x)
a executed
>>> b()
[5]
>>> b()
[5, 5]
对我来说,似乎相对是相对的,这似乎是相对的在哪里放置参数范围:函数内部或与之“一起”?
在函数内部进行绑定意味着 x
在调用(而不是定义)时有效地绑定到指定的默认值,而这些功能会带来深处的缺陷: def
从某种意义上说,线将是“混合”的,因为(函数对象的绑定对象)的一部分将在定义上发生,并且在函数调用时间时(默认参数的分配)。
实际行为更加一致:执行该线时,该行的所有内容都将在函数定义上进行评估。
Anyone tinkering with Python long enough has been bitten (or torn to pieces) by the following issue:
def foo(a=[]):
a.append(5)
return a
Python novices would expect this function called with no parameter to always return a list with only one element: [5]
. The result is instead very different, and very astonishing (for a novice):
>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()
A manager of mine once had his first encounter with this feature, and called it "a dramatic design flaw" of the language. I replied that the behavior had an underlying explanation, and it is indeed very puzzling and unexpected if you don't understand the internals. However, I was not able to answer (to myself) the following question: what is the reason for binding the default argument at function definition, and not at function execution? I doubt the experienced behavior has a practical use (who really used static variables in C, without breeding bugs?)
Edit:
Baczek made an interesting example. Together with most of your comments and Utaal's in particular, I elaborated further:
def a():
print("a executed")
return []
def b(x=a()):
x.append(5)
print(x)
a executed
>>> b()
[5]
>>> b()
[5, 5]
To me, it seems that the design decision was relative to where to put the scope of parameters: inside the function, or "together" with it?
Doing the binding inside the function would mean that x
is effectively bound to the specified default when the function is called, not defined, something that would present a deep flaw: the def
line would be "hybrid" in the sense that part of the binding (of the function object) would happen at definition, and part (assignment of default parameters) at function invocation time.
The actual behavior is more consistent: everything of that line gets evaluated when that line is executed, meaning at function definition.
发布评论
评论(30)
可能是的确:
保留上面的两个功能并仍然提出另一个要点是完全一致的:
其他答案,或者至少有些答案要么是第1和2,而不是3,要么是第3点和淡淡的点1和2
。在这里中游的马匹将要求大量破裂,并且通过更改Python来直观地处理Stefano的开场片段,可能会造成更多问题。而且,一个很好的人可以很好地解释后果的雷区。 但是,
现有的行为不是Pythonic,而Python是成功的,因为对语言的很少违反了最少令人惊讶的原则。这是一个真正的问题,无论将其连根拔起是否明智。这是一个设计缺陷。如果您通过试图发现行为更好地理解语言,我可以说C ++可以做到所有这些以及更多。您可以通过导航,例如微妙的指针错误来学到很多东西。但这不是pythonic:面对这种行为的人足够关心python的人是被吸引这种语言的人,因为Python的惊喜比其他语言要少得多。当他们感到惊讶的是,涉水和好奇的人在花费很少的时间来实现某种工作时(不是因为设计fl)而感到惊讶 - 我的意思是,隐藏的逻辑难题 - 与被吸引到python的程序员的直觉相比因为它只是起作用。
It may be true that:
it is entirely consistent to hold to both of the features above and still make another point:
The other answers, or at least some of them either make points 1 and 2 but not 3, or make point 3 and downplay points 1 and 2. But all three are true.
It may be true that switching horses in midstream here would be asking for significant breakage, and that there could be more problems created by changing Python to intuitively handle Stefano's opening snippet. And it may be true that someone who knew Python internals well could explain a minefield of consequences. However,
The existing behavior is not Pythonic, and Python is successful because very little about the language violates the principle of least astonishment anywhere near this badly. It is a real problem, whether or not it would be wise to uproot it. It is a design flaw. If you understand the language much better by trying to trace out the behavior, I can say that C++ does all of this and more; you learn a lot by navigating, for instance, subtle pointer errors. But this is not Pythonic: people who care about Python enough to persevere in the face of this behavior are people who are drawn to the language because Python has far fewer surprises than other language. Dabblers and the curious become Pythonistas when they are astonished at how little time it takes to get something working--not because of a design fl--I mean, hidden logic puzzle--that cuts against the intuitions of programmers who are drawn to Python because it Just Works.
一个简单的解决方法
A simple workaround using None
我将演示一种替代结构,以将默认列表值传递给函数(与词典同样效果很好)。
正如其他人广泛评论的那样,列表参数定义时与该函数(而不是执行)绑定。由于列表和词典是可变的,因此对此参数的任何更改都会影响对此功能的其他调用。结果,随后对该功能的调用将接收此共享列表,这些列表可能已被任何其他调用该功能更改。更糟糕的是,两个参数正在使用此函数的共享参数,同时却忽略了对方所做的更改。
错误的方法(可能...):
来验证它们是一个和同一对象
您可以通过使用
id
:每个Brett Slatkin的“有效python:59个特定的写作方法) 更好的python”,项目20:使用无
和docstrings指定动态默认参数(第48页)此实现可确保每个呼叫对函数的调用要么接收默认列表,否则将传递给该功能的列表。
首选方法:
“错误的方法”可能存在合法用例,因此程序员打算要共享默认列表参数,但这比规则更可能是例外。
I am going to demonstrate an alternative structure to pass a default list value to a function (it works equally well with dictionaries).
As others have extensively commented, the list parameter is bound to the function when it is defined as opposed to when it is executed. Because lists and dictionaries are mutable, any alteration to this parameter will affect other calls to this function. As a result, subsequent calls to the function will receive this shared list which may have been altered by any other calls to the function. Worse yet, two parameters are using this function's shared parameter at the same time oblivious to the changes made by the other.
Wrong Method (probably...):
You can verify that they are one and the same object by using
id
:Per Brett Slatkin's "Effective Python: 59 Specific Ways to Write Better Python", Item 20: Use
None
and Docstrings to specify dynamic default arguments (p. 48)This implementation ensures that each call to the function either receives the default list or else the list passed to the function.
Preferred Method:
There may be legitimate use cases for the 'Wrong Method' whereby the programmer intended the default list parameter to be shared, but this is more likely the exception than the rule.
是的,这是Python中的设计缺陷。
我已经阅读了所有其他答案,但我没有说服。此设计确实违反了 “最不惊讶的原则” 。
默认值可能是在调用函数时而不是定义函数时评估的。这就是JavaScript的方式:
作为进一步的证据,这是一个设计缺陷,Python Core开发人员目前正在讨论引入新的语法以解决此问题。请参阅本文: python的后期参数默认值。
对于更多证据表明,这是一个设计缺陷,如果您Google google“ python gotchas”,则将这种设计称为陷阱,通常是列表中的第一个gotcha,在前9个Google结果中( 1 , 2 , 3 , 4 , 5 , 6 , 7 //www.reddit.com/r/learnpython/comments/2xwc6s/python_gotchas/“ rel =“ nofollow noreferrer”> 8 , 9 )。相比之下,如果您Google“ JavaScript Gotchas”,那么JavaScript中默认论点的行为甚至都不会被提及一次。
从定义上讲,Gotchas违反了至少惊讶的原则。他们惊讶。鉴于有针对默认参数值的行为的超级设计,因此不可避免的结论是,这里的Python的行为代表了一个设计缺陷。
我说这是一个爱Python的人。我们可以成为Python的粉丝,并且仍然承认,每个对Python这一方面感到不愉快的人都感到惊讶,因为它是真正的“ Gotcha”,因为它是是。
Yes, this is a design flaw in Python.
I've read all the other answers and I'm not convinced. This design does violate the "Principle of least astonishment".
The defaults could have been designed to be evaluated when the function is called, rather than when the function is defined. This is how JavaScript does it:
As further evidence that this is a design flaw, Python core developers are currently discussing introducing new syntax to fix this problem. See this article: Late-bound argument defaults for Python.
For even more evidence that this a design flaw, if you Google "Python gotchas", this design is mentioned as a gotcha, usually the first gotcha in the list, in the first 9 Google results (1, 2, 3, 4, 5, 6, 7, 8, 9). In contrast, if you Google "JavaScript gotchas", the behaviour of default arguments in JavaScript is not mentioned as a gotcha even once.
Gotchas, by definition, violate the principle of least astonishment. They astonish. Given there are superiour designs for the behaviour of default argument values, the inescapable conclusion is that Python's behaviour here represents a design flaw.
I say this as someone who loves Python. We can be fans of Python, and still admit that everyone who is unpleasantly surprised by this aspect of Python is unpleasantly surprised because it is a genuine "gotcha".
这里的解决方案是:
无
作为默认值(或nonceobject
),然后打开它以在运行时创建您的值;或lambda
作为默认参数,然后在尝试块中调用它以获取默认值(这是Lambda Abstraction的一种方法)。第二个选项很不错,因为该函数的用户可以通过可召唤传递,该函数可能已经存在(例如
type
)The solutions here are:
None
as your default value (or a nonceobject
), and switch on that to create your values at runtime; orlambda
as your default parameter, and call it within a try block to get the default value (this is the sort of thing that lambda abstraction is for).The second option is nice because users of the function can pass in a callable, which may be already existing (such as a
type
)您可以通过替换对象(因此与范围的领带)来解决这个问题:
丑陋,但可以使用。
You can get round this by replacing the object (and therefore the tie with the scope):
Ugly, but it works.
当我们执行此操作时:
...我们将参数 a 分配给 未命名列表,如果呼叫者不传递a的值。
为了使事情变得更简单,让我们暂时给未命名的列表一个名称。
pavlo
怎么样?在任何时候,如果呼叫者不告诉我们
a
是什么,我们将重复使用pavlo
。如果
pavlo
是可变的(可修改),并且foo
最终对其进行了修改,那么我们注意到下一次foo
未指定foo
> a 。因此,这就是您看到的(请记住,
pavlo
被初始化为[]):现在,
pavlo
is [5]。呼叫
foo()
再次修改pavlo
再次:指定
a
时 foo> foo()确保pavlo <
pavlo << /代码>未触摸。
因此,
pavlo
仍然是[5,5]
。When we do this:
... we assign the argument
a
to an unnamed list, if the caller does not pass the value of a.To make things simpler for this discussion, let's temporarily give the unnamed list a name. How about
pavlo
?At any time, if the caller doesn't tell us what
a
is, we reusepavlo
.If
pavlo
is mutable (modifiable), andfoo
ends up modifying it, an effect we notice the next timefoo
is called without specifyinga
.So this is what you see (Remember,
pavlo
is initialized to []):Now,
pavlo
is [5].Calling
foo()
again modifiespavlo
again:Specifying
a
when callingfoo()
ensurespavlo
is not touched.So,
pavlo
is still[5, 5]
.我有时会利用这种行为作为以下模式的替代方法:
如果
singleton
仅由> use_singleton
使用,我喜欢以下模式作为替代:我已经将其用于实例化访问外部资源的客户端类,也可以创建记忆的命令或列表。
由于我认为这种模式并不众所周知,因此我确实发表了简短的评论,以防止将来的误解。
I sometimes exploit this behavior as an alternative to the following pattern:
If
singleton
is only used byuse_singleton
, I like the following pattern as a replacement:I've used this for instantiating client classes that access external resources, and also for creating dicts or lists for memoization.
Since I don't think this pattern is well known, I do put a short comment in to guard against future misunderstandings.
每个其他答案都解释了为什么这实际上是一种不错的所需行为,或者为什么您无论如何都不需要这个。我的是为那些想要行使自己的权利将语言屈服于自己的意志而不是相反的顽固的人。
我们将使用装饰器“修复”此行为,该行为将复制默认值,而不是重复使用其默认值的每个位置参数相同的实例。
现在,让我们使用此装饰器重新定义我们的功能:
对于采用多个参数的功能,这尤其整洁。比较:
重要的是
要注意,如果您尝试使用关键字args,则上述解决方案会破裂,例如:
可以调整装饰器以允许这样做,但是我们将其作为读者的练习;)
Every other answer explains why this is actually a nice and desired behavior, or why you shouldn't be needing this anyway. Mine is for those stubborn ones who want to exercise their right to bend the language to their will, not the other way around.
We will "fix" this behavior with a decorator that will copy the default value instead of reusing the same instance for each positional argument left at its default value.
Now let's redefine our function using this decorator:
This is particularly neat for functions that take multiple arguments. Compare:
with
It's important to note that the above solution breaks if you try to use keyword args, like so:
The decorator could be adjusted to allow for that, but we leave this as an exercise for the reader ;)
这个“错误”给了我很多加班工作时间!但是我开始看到它的潜在用途(但是我希望它能在执行时间处于执行时),
我会给您我认为的一个有用的例子。
打印以下内容
This "bug" gave me a lot of overtime work hours! But I'm beginning to see a potential use of it (but I would have liked it to be at the execution time, still)
I'm gonna give you what I see as a useful example.
prints the following
这不是设计缺陷。任何绊倒这一点的人都做错了什么。
我有3种情况看到您可能会遇到此问题的位置:
cache = {}
,就不会期望使用实际参数调用该函数。问题中的示例可能属于第1类或3。奇怪的是,它既可以修改传递的列表又返回。您应该选择一个或另一个。
This is not a design flaw. Anyone who trips over this is doing something wrong.
There are 3 cases I see where you might run into this problem:
cache={}
, and you wouldn't be expected to call the function with an actual argument at all.The example in the question could fall into category 1 or 3. It's odd that it both modifies the passed list and returns it; you should pick one or the other.
只需将功能更改为:
Just change the function to be:
TLDR:定义时间默认值是一致的,并且严格表现得更加表现力。
定义功能会影响两个范围:定义范围包含函数,而执行范围由 包含。虽然很清楚地块映射是如何示为示例的,但问题是
def&lt; name&gt;(&lt; args = defaults&gt;):
属于:def/code name
part 必须在定义范围中评估 - 我们希望name
毕竟在那里可用。仅在内部评估功能将使其无法访问。由于
参数
是一个常数名称,因此我们可以与def name
同时“评估”它。这也具有其优势,它以已知的签名为name(parameter = ...):
,而不是裸露的name(...):
。现在,何时评估
默认
?一致性已经在“定义上”:
def&lt; name&gt;(&lt; args = defaults&gt;)的所有内容:
也可以在定义上进行评估。延迟部分将是令人惊讶的选择。这两个选择也不等于:如果在定义时间评估
默认
,则仍然可以影响执行时间。如果在执行时间评估默认,则它不能影响定义时间。选择“定义”允许表达这两种情况,而选择“执行”只能表达一个情况:TLDR: Define-time defaults are consistent and strictly more expressive.
Defining a function affects two scopes: the defining scope containing the function, and the execution scope contained by the function. While it is pretty clear how blocks map to scopes, the question is where
def <name>(<args=defaults>):
belongs to:The
def name
part must evaluate in the defining scope - we wantname
to be available there, after all. Evaluating the function only inside itself would make it inaccessible.Since
parameter
is a constant name, we can "evaluate" it at the same time asdef name
. This also has the advantage it produces the function with a known signature asname(parameter=...):
, instead of a barename(...):
.Now, when to evaluate
default
?Consistency already says "at definition": everything else of
def <name>(<args=defaults>):
is best evaluated at definition as well. Delaying parts of it would be the astonishing choice.The two choices are not equivalent, either: If
default
is evaluated at definition time, it can still affect execution time. Ifdefault
is evaluated at execution time, it cannot affect definition time. Choosing "at definition" allows expressing both cases, while choosing "at execution" can express only one:实际上,这不是设计缺陷,也不是因为内部或性能。它仅仅是从python中的函数 第一级对象 ,不仅是一块代码。
一旦您以这种方式想到它,就完全有意义:一个函数是对其定义进行评估的对象;默认参数是“成员数据” ,因此它们的状态可能会从一个呼叫变为另一个呼叫 - 与其他任何对象一样。
无论如何,effbot( fredrik lundh )对这种行为的原因有很好的解释在。我发现它非常清楚,我真的建议阅读它,以更好地了解功能对象的工作方式。
Actually, this is not a design flaw, and it is not because of internals or performance. It comes simply from the fact that functions in Python are first-class objects, and not only a piece of code.
As soon as you think of it this way, then it completely makes sense: a function is an object being evaluated on its definition; default parameters are kind of "member data" and therefore their state may change from one call to the other - exactly as in any other object.
In any case, the Effbot (Fredrik Lundh) has a very nice explanation of the reasons for this behavior in Default Parameter Values in Python. I found it very clear, and I really suggest reading it for a better knowledge of how function objects work.
假设当我看到饮食声明时,您有以下代码
,至少令人惊讶的事情是,如果没有给出第一个参数,它将等于元组
(“苹果”,“ bananas”,“ bananas”, 但是, “ loganberries”)
,但是,在代码中稍后假设,我会做一些事情,
然后如果默认参数在函数执行中绑定而不是函数声明,我会惊讶(以一种非常糟糕的方式)发现水果已更改。这比发现您的
foo
函数正在突变列表更令人惊讶。真正的问题在于可变变量,所有语言在某种程度上都有这个问题。这是一个问题:假设在Java中我有以下代码:
现在,我的地图是否使用
StringBuffer
键的值,或者将其放置在地图中时,还是通过引用存储密钥?无论哪种方式,有人都惊讶。试图将对象从MAP
使用的人使用与他们放入的对象相同的人,或者即使他们的对象也无法检索其对象的键'实际上是与用来将其放入地图上的对象相同的(这实际上,这就是为什么Python不允许其可变的内置数据类型用作字典键)。您的示例是一个很好的案例,新来者会感到惊讶和咬伤。但是我认为,如果我们“修复”了这一点,那么这只会造成一种不同的情况,而它们会被咬伤,而那个情况的直觉甚至更少。此外,处理可变变量时始终是这种情况。您总是会遇到某人可以直观地期待一种或相反行为的情况,具体取决于他们编写的代码。
我个人喜欢Python的当前方法:定义函数时评估默认函数参数,并且该对象始终是默认值。我想他们可以使用空列表特别案例,但是这种特殊的套管会引起更多的惊讶,更不用说向后不兼容了。
Suppose you have the following code
When I see the declaration of eat, the least astonishing thing is to think that if the first parameter is not given, that it will be equal to the tuple
("apples", "bananas", "loganberries")
However, suppose later on in the code, I do something like
then if default parameters were bound at function execution rather than function declaration, I would be astonished (in a very bad way) to discover that fruits had been changed. This would be more astonishing IMO than discovering that your
foo
function above was mutating the list.The real problem lies with mutable variables, and all languages have this problem to some extent. Here's a question: suppose in Java I have the following code:
Now, does my map use the value of the
StringBuffer
key when it was placed into the map, or does it store the key by reference? Either way, someone is astonished; either the person who tried to get the object out of theMap
using a value identical to the one they put it in with, or the person who can't seem to retrieve their object even though the key they're using is literally the same object that was used to put it into the map (this is actually why Python doesn't allow its mutable built-in data types to be used as dictionary keys).Your example is a good one of a case where Python newcomers will be surprised and bitten. But I'd argue that if we "fixed" this, then that would only create a different situation where they'd be bitten instead, and that one would be even less intuitive. Moreover, this is always the case when dealing with mutable variables; you always run into cases where someone could intuitively expect one or the opposite behavior depending on what code they're writing.
I personally like Python's current approach: default function arguments are evaluated when the function is defined and that object is always the default. I suppose they could special-case using an empty list, but that kind of special casing would cause even more astonishment, not to mention be backwards incompatible.
documentation :
The relevant part of the documentation:
我对Python口译员的内部运作一无所知(而且我也不是编译器和口译员的专家),所以如果我提出任何不友善或不可能的事情,请不要怪我。
只要Python对象是可变的,我认为在设计默认参数的内容时应考虑这一点。
当您实例化列表时:
您希望获得新 a 的列表。
中的
a = []
为什么在实例化功能定义的新列表中,而不是调用中的新列表 ?
就像您在问“如果用户不提供参数,那么 bactantiate 一个新列表,并将其像呼叫者产生一样使用”。
我认为这是模棱两可的:
用户,您是否希望
a
默认到与定义或执行x
相对应的日期时间?在这种情况下,就像在上一篇中一样,我将保持相同的行为,就像默认参数“分配”是该函数的第一个指令(
dateTime.now()
on Function Invocation) 。另一方面,如果用户想要他可以写的定义时间映射:
我知道,我知道:这是一个封闭。另外,Python可能会提供一个关键字来强制定义时间绑定:
I know nothing about the Python interpreter inner workings (and I'm not an expert in compilers and interpreters either) so don't blame me if I propose anything unsensible or impossible.
Provided that python objects are mutable I think that this should be taken into account when designing the default arguments stuff.
When you instantiate a list:
you expect to get a new list referenced by
a
.Why should the
a=[]
ininstantiate a new list on function definition and not on invocation?
It's just like you're asking "if the user doesn't provide the argument then instantiate a new list and use it as if it was produced by the caller".
I think this is ambiguous instead:
user, do you want
a
to default to the datetime corresponding to when you're defining or executingx
?In this case, as in the previous one, I'll keep the same behaviour as if the default argument "assignment" was the first instruction of the function (
datetime.now()
called on function invocation).On the other hand, if the user wanted the definition-time mapping he could write:
I know, I know: that's a closure. Alternatively Python might provide a keyword to force definition-time binding:
好吧,原因很简单,当执行代码时完成绑定,并且执行函数定义,很好...定义函数时。
比较以下内容:
此代码遭受了完全相同的意外事件。香蕉是一个类属性,因此,当您将内容添加到其中时,它将添加到该类的所有实例中。原因完全相同。
它只是“它的工作原理”,使其在功能情况下的工作方式有所不同,在班级情况下可能是不可能的,或者至少会慢慢放慢对象实例化,因为您必须将类代码保留并在创建对象时执行它。
是的,这是出乎意料的。但是,一旦一分钱掉下来,它就完全符合Python的总体运作方式。实际上,这是一个很好的教学援助,一旦您明白了为什么会发生这种情况,您就会更好地抓住python。
也就是说,在任何优秀的Python教程中都应该以突出的特征。因为正如您提到的那样,每个人迟早会遇到这个问题。
Well, the reason is quite simply that bindings are done when code is executed, and the function definition is executed, well... when the functions is defined.
Compare this:
This code suffers from the exact same unexpected happenstance. bananas is a class attribute, and hence, when you add things to it, it's added to all instances of that class. The reason is exactly the same.
It's just "How It Works", and making it work differently in the function case would probably be complicated, and in the class case likely impossible, or at least slow down object instantiation a lot, as you would have to keep the class code around and execute it when objects are created.
Yes, it is unexpected. But once the penny drops, it fits in perfectly with how Python works in general. In fact, it's a good teaching aid, and once you understand why this happens, you'll grok python much better.
That said it should feature prominently in any good Python tutorial. Because as you mention, everyone runs into this problem sooner or later.
你为什么不进行内省?
我真的感到惊讶的是,没有人在Callables上执行Python(
2
3 应用)提供的有见地的内省。给定一个简单的小函数
func
定义为:当Python遇到它时,它将要做的第一件事是编译它以创建此功能的
code
对象。在完成此编译步骤的同时, python 评估*,然后商店在此处默认参数[]
在此处)功能对象本身。如上所述:列表a
现在可以将其视为函数func
的成员。因此,让我们进行一些内省,然后检查列表如何扩展 函数对象。我正在使用
python 3.x
为此,对于Python 2相同(使用__默认值__
或func_defaults
in Python 2;是的同一件事的名称)。执行前的功能:
Python执行此定义后,将采用指定的任何默认参数(
a = []
此处)和在__ defaults _____
函数对象的属性(相关部分:callables:callables:callables:callables:callables):好的空列表是
__中的单个条目,默认为__
,就像预期一样。执行后的功能:
现在让我们执行此函数:
现在,让我们查看这些
__默认值__
再次:惊讶吗?对象内部的值会更改!现在,连续呼叫对函数的连续调用将简单地附加到该嵌入式
list
对象:因此,您有,此'flaw'发生的原因是,是因为默认参数为功能对象的一部分。这里没有什么奇怪的事情,这有点令人惊讶。
战斗的常见解决方案是使用
none
作为默认值,然后在功能正文中初始化:由于每次都会重新执行功能主体,因此,如果没有参数为通过
a
传递。要进一步验证
__默认列表__
与函数func
中使用的列表相同列表a
在功能主体内使用。然后,将其与__默认__
中的列表进行比较(位置[0] [0]同一列表实例:
全部具有内省的力量!
函数编译过程中评估默认参数
*要验证python在 该功能并将其绑定到名称
bar
。Why don't you introspect?
I'm really surprised no one has performed the insightful introspection offered by Python (
2
and3
apply) on callables.Given a simple little function
func
defined as:When Python encounters it, the first thing it will do is compile it in order to create a
code
object for this function. While this compilation step is done, Python evaluates* and then stores the default arguments (an empty list[]
here) in the function object itself. As the top answer mentioned: the lista
can now be considered a member of the functionfunc
.So, let's do some introspection, a before and after to examine how the list gets expanded inside the function object. I'm using
Python 3.x
for this, for Python 2 the same applies (use__defaults__
orfunc_defaults
in Python 2; yes, two names for the same thing).Function Before Execution:
After Python executes this definition it will take any default parameters specified (
a = []
here) and cram them in the__defaults__
attribute for the function object (relevant section: Callables):O.k, so an empty list as the single entry in
__defaults__
, just as expected.Function After Execution:
Let's now execute this function:
Now, let's see those
__defaults__
again:Astonished? The value inside the object changes! Consecutive calls to the function will now simply append to that embedded
list
object:So, there you have it, the reason why this 'flaw' happens, is because default arguments are part of the function object. There's nothing weird going on here, it's all just a bit surprising.
The common solution to combat this is to use
None
as the default and then initialize in the function body:Since the function body is executed anew each time, you always get a fresh new empty list if no argument was passed for
a
.To further verify that the list in
__defaults__
is the same as that used in the functionfunc
you can just change your function to return theid
of the lista
used inside the function body. Then, compare it to the list in__defaults__
(position[0]
in__defaults__
) and you'll see how these are indeed refering to the same list instance:All with the power of introspection!
* To verify that Python evaluates the default arguments during compilation of the function, try executing the following:
as you'll notice,
input()
is called before the process of building the function and binding it to the namebar
is made.我曾经认为在运行时创建对象将是更好的方法。我现在不太确定,因为您确实失去了一些有用的功能,尽管不管仅仅防止新手困惑,也可能值得。这样做的缺点是:
1。绩效
如果使用了呼叫时间评估,则每次使用您的功能而无需参数时,都会调用昂贵的功能。您要么在每个电话上支付昂贵的价格,要么需要在外部手动缓存该值,从而污染您的名称空间并增加了冗长的态度。
2。强迫绑定参数
一个有用的技巧是在创建lambda时将lambda的参数绑定到变量的电流结合。例如:
这分别返回返回0,1,2,3 ...的功能列表。如果行为更改,则他们将将
i
绑定到i的 call time i值,因此您将获得所有返回9
9 <的功能列表。 /代码>。
实施此操作的唯一方法是使用I BOUND,即:
3创建进一步的封闭。内省
考虑代码:
我们可以使用
Inspect
模块获取有关参数和默认值的信息,此信息对于文档生成,元编程,装饰器等诸如此信息非常有用。
现在,假设可以更改默认值的行为,以使其等同于:
但是,我们失去了内省的能力,并查看默认参数是什么。因为尚未构造对象,所以我们永远无法抓住它们,而无需实际调用该功能。我们能做的最好的方法是存储源代码并将其作为字符串返回。
I used to think that creating the objects at runtime would be the better approach. I'm less certain now, since you do lose some useful features, though it may be worth it regardless simply to prevent newbie confusion. The disadvantages of doing so are:
1. Performance
If call-time evaluation is used, then the expensive function is called every time your function is used without an argument. You'd either pay an expensive price on each call, or need to manually cache the value externally, polluting your namespace and adding verbosity.
2. Forcing bound parameters
A useful trick is to bind parameters of a lambda to the current binding of a variable when the lambda is created. For example:
This returns a list of functions that return 0,1,2,3... respectively. If the behaviour is changed, they will instead bind
i
to the call-time value of i, so you would get a list of functions that all returned9
.The only way to implement this otherwise would be to create a further closure with the i bound, ie:
3. Introspection
Consider the code:
We can get information about the arguments and defaults using the
inspect
module, whichThis information is very useful for things like document generation, metaprogramming, decorators etc.
Now, suppose the behaviour of defaults could be changed so that this is the equivalent of:
However, we've lost the ability to introspect, and see what the default arguments are. Because the objects haven't been constructed, we can't ever get hold of them without actually calling the function. The best we could do is to store off the source code and return that as a string.
捍卫Python
简单性的5分:从以下意义上讲,行为很简单:
大多数人只陷入一个陷阱一次,而不是几次。
一致性:Python 始终通过对象,而不是名称。
默认参数显然是该函数的一部分
标题(不是功能主体)。因此,应该评估它
在模块加载时间(仅在模块加载时间,除非嵌套),而不是
在功能致电时间。
有用性:正如弗雷德里克·伦德(Frederik Lundh)在他的解释中指出的那样
,
当前行为对于高级编程可能非常有用。
(很少使用。)
足够的文档:在最基本的Python文档中,
教程,这个问题大声宣布为
“重要警告” 在首先小节中
“有关定义函数的更多信息” 。
警告甚至使用粗体,
很少在标题外应用。
RTFM:阅读精美的手册。
元学习:掉入陷阱实际上是一个非常
有用的时刻(至少如果您是反思性学习者),
因为随后您会更好地理解这一点
上面的“一致性”,那将
教您关于Python的很多东西。
5 points in defense of Python
Simplicity: The behavior is simple in the following sense:
Most people fall into this trap only once, not several times.
Consistency: Python always passes objects, not names.
The default parameter is, obviously, part of the function
heading (not the function body). It therefore ought to be evaluated
at module load time (and only at module load time, unless nested), not
at function call time.
Usefulness: As Frederik Lundh points out in his explanation
of "Default Parameter Values in Python", the
current behavior can be quite useful for advanced programming.
(Use sparingly.)
Sufficient documentation: In the most basic Python documentation,
the tutorial, the issue is loudly announced as
an "Important warning" in the first subsection of Section
"More on Defining Functions".
The warning even uses boldface,
which is rarely applied outside of headings.
RTFM: Read the fine manual.
Meta-learning: Falling into the trap is actually a very
helpful moment (at least if you are a reflective learner),
because you will subsequently better understand the point
"Consistency" above and that will
teach you a great deal about Python.
此行为容易由以下解释:
so:
a
不更改 - 每个分配调用都创建新的int int int int对象 - 新对象是打印b
不更改的 - 新数组是从默认值构建的,并打印c
更改 - 操作是在同一对象上执行的,并且它是打印的This behavior is easy explained by:
So:
a
doesn't change - every assignment call creates new int object - new object is printedb
doesn't change - new array is build from default value and printedc
changes - operation is performed on same object - and it is printed1)“可变默认参数”的所谓问题通常是一个特殊的例子,证明了:
“此问题的所有功能也遭受了实际参数的类似副作用问题,”
这违反了功能编程的规则,通常是不受欢迎的,应将两者都固定在一起。
示例:
解决方案:a 复制
一个绝对安全的解决方案是
复制
或deepcopy
首先输入对象,然后对副本进行任何操作。许多内置的可变类型都有一个复制方法,例如
some_dict.copy()
或some_set.copy()
或可以轻松复制,例如somelist [:]
或list(some_list)
。每个对象也可以通过copy.copy(any_object)
或更彻底的copy.deepcopy()
(如果可突变的对象从可突变对象组成)有用)来复制每个对象。 。某些对象从根本上是基于诸如“文件”对象的副作用,并且无法通过副本进行有意义的复制。 复制=“ https://stackoverflow.com/q/13484107/448474”>一个类似的问题
它不应保存在此功能返回的实例的任何 public 属性中。 (假设不应通过惯例从本类或子类中修改实例的 private
。
输入参数对象不应对象进行修改(突变),也不应将它们束缚到函数返回的对象中。 (如果我们在没有副作用的情况下进行预先编程,这是强烈建议的。请参见 wiki /a>(在此上下文中,前两个段落是相关的。)
。)
2)
只有当需要对实际参数的副作用,但对默认参数不需要,则有用的解决方案为
def ...(var1 = none):
如果var1是无:
var1 = []
更多..更多..3)在某些情况下是默认参数有用。
1) The so-called problem of "Mutable Default Argument" is in general a special example demonstrating that:
"All functions with this problem suffer also from similar side effect problem on the actual parameter,"
That is against the rules of functional programming, usually undesiderable and should be fixed both together.
Example:
Solution: a copy
An absolutely safe solution is to
copy
ordeepcopy
the input object first and then to do whatever with the copy.Many builtin mutable types have a copy method like
some_dict.copy()
orsome_set.copy()
or can be copied easy likesomelist[:]
orlist(some_list)
. Every object can be also copied bycopy.copy(any_object)
or more thorough bycopy.deepcopy()
(the latter useful if the mutable object is composed from mutable objects). Some objects are fundamentally based on side effects like "file" object and can not be meaningfully reproduced by copy. copyingExample problem for a similar SO question
It shouldn't be neither saved in any public attribute of an instance returned by this function. (Assuming that private attributes of instance should not be modified from outside of this class or subclasses by convention. i.e.
_var1
is a private attribute )Conclusion:
Input parameters objects shouldn't be modified in place (mutated) nor they should not be binded into an object returned by the function. (If we prefere programming without side effects which is strongly recommended. see Wiki about "side effect" (The first two paragraphs are relevent in this context.)
.)
2)
Only if the side effect on the actual parameter is required but unwanted on the default parameter then the useful solution is
def ...(var1=None):
if var1 is None:
var1 = []
More..3) In some cases is the mutable behavior of default parameters useful.
您要问的是为什么:
在内部不等同:
除了明确调用func(无,无)的情况,我们将忽略。
换句话说,为什么不存储每个参数,而不是评估默认参数,并在调用函数时对其进行评估?
一个答案可能就在那里 - 它可以有效地将每个功能带有默认参数转换为封闭。即使全部隐藏在口译员中,而不是一个成熟的封闭,数据也必须存储在某个地方。它会更慢并使用更多内存。
What you're asking is why this:
isn't internally equivalent to this:
except for the case of explicitly calling func(None, None), which we'll ignore.
In other words, instead of evaluating default parameters, why not store each of them, and evaluate them when the function is called?
One answer is probably right there--it would effectively turn every function with default parameters into a closure. Even if it's all hidden away in the interpreter and not a full-blown closure, the data's got to be stored somewhere. It'd be slower and use more memory.
实际上,这与默认值无关,除非当您编写具有可变默认值的功能时,它通常是出乎意料的行为。
此代码中没有默认值,但是您会遇到完全相同的问题。
问题在于
foo
是修改 当呼叫者不期望这是一个可突变的变量。如果称为append_5
之类的函数,则这样的代码就可以了。然后,呼叫者将调用该功能以修改其传递的值,并可以预期行为。但是,这样的函数不太可能采用默认参数,并且可能不会返回列表(因为呼叫者已经对该列表有一个引用;它刚刚通过的列表)。您的原始
foo
带有默认参数,不应修改a
是否已明确传递或获得默认值。您的代码应仅留下可变的参数,除非从上下文/名称/文档清楚地表明应该修改这些参数。无论我们是在Python中,是否涉及默认参数,使用作为本地临时性作为本地临时性作为本地临时性的参数是一个非常糟糕的主意。如果您需要在计算某些内容的过程中破坏性地操纵本地临时性,并且需要从参数值开始操纵,则需要制作副本。
This actually has nothing to do with default values, other than that it often comes up as an unexpected behaviour when you write functions with mutable default values.
No default values in sight in this code, but you get exactly the same problem.
The problem is that
foo
is modifying a mutable variable passed in from the caller, when the caller doesn't expect this. Code like this would be fine if the function was called something likeappend_5
; then the caller would be calling the function in order to modify the value they pass in, and the behaviour would be expected. But such a function would be very unlikely to take a default argument, and probably wouldn't return the list (since the caller already has a reference to that list; the one it just passed in).Your original
foo
, with a default argument, shouldn't be modifyinga
whether it was explicitly passed in or got the default value. Your code should leave mutable arguments alone unless it is clear from the context/name/documentation that the arguments are supposed to be modified. Using mutable values passed in as arguments as local temporaries is an extremely bad idea, whether we're in Python or not and whether there are default arguments involved or not.If you need to destructively manipulate a local temporary in the course of computing something, and you need to start your manipulation from an argument value, you need to make a copy.
默认参数的默认
Python:在程序运行时开始,将函数汇编为函数对象时,将评估 参数默认参数。当函数使用多次函数时,它们是内存中的并保持相同的对象,而当突变(如果对象是可突变类型的对象)时,它们仍会在连续呼叫上突变。
它们被突变并保持突变,因为它们每次调用函数时都是相同的对象。
等效代码:
由于列表被编译和实例化时列表绑定到函数,因此:
几乎完全等同于此:
演示
每次引用它们是相同的对象
example.py
并使用
python example.py
:这是否违反了原理“最不惊讶”?
该执行顺序经常使Python的新用户感到困惑。如果您了解Python执行模型,那么它就会非常期待。
给新的Python用户的通常说明:
这就是为什么向新用户提供的通常说明是为了创建这样的默认参数:
这将None Singleton用作前哨对象来告诉该功能我们是否获得了参数除了默认。如果我们没有参数,那么我们实际上想使用一个新的空列表,
[]
作为默认值。作为控制流的教程部分>“ noreferrer”>教程部分
Python: The Mutable Default Argument
Default arguments get evaluated at the time the function is compiled into a function object, at the start of the program runtime. When used by the function, multiple times by that function, they are and remain the same object in memory, and when mutated (if the object is of a mutable type) they remain mutated on consecutive calls.
They are mutated and stay mutated because they are the same object each time the function is called.
Equivalent code:
Since the list is bound to the function when the function object is compiled and instantiated, this:
is almost exactly equivalent to this:
Demonstration
Here's a demonstration - you can verify that they are the same object each time they are referenced by
example.py
and running it with
python example.py
:Does this violate the principle of "Least Astonishment"?
This order of execution is frequently confusing to new users of Python. If you understand the Python execution model, then it becomes quite expected.
The usual instruction to new Python users:
But this is why the usual instruction to new users is to create their default arguments like this instead:
This uses the None singleton as a sentinel object to tell the function whether or not we've gotten an argument other than the default. If we get no argument, then we actually want to use a new empty list,
[]
, as the default.As the tutorial section on control flow says:
已经很忙的话题,但是从我在这里阅读的内容,以下内容帮助我意识到它在内部的工作方式:
Already busy topic, but from what I read here, the following helped me realizing how it's working internally:
最短的答案可能是“定义是执行”,因此整个论点没有严格的意义。作为一个更为人为的示例,您可能会引用这一点:
希望在
def
语句的执行时间内不执行默认参数表达式不容易或没有任何意义,或者不合理,或者两个都。但是,当您尝试使用默认构造函数时,我同意这是一个陷阱。
The shortest answer would probably be "definition is execution", therefore the whole argument makes no strict sense. As a more contrived example, you may cite this:
Hopefully it's enough to show that not executing the default argument expressions at the execution time of the
def
statement isn't easy or doesn't make sense, or both.I agree it's a gotcha when you try to use default constructors, though.
这是性能优化。由于此功能,您认为这两个函数调用中的哪一个更快?
我给你一个提示。这是拆卸(请参阅 http://docs.python.org/library/library/dis.html ):
<代码># 1
#
2您可以看到,使用不可分割的默认参数时,是 。如果经常称为函数或默认参数需要很长时间才能构造,则可能会有所作为。另外,请记住,Python不是C。在C中,您的常数几乎是免费的。在Python中,您没有这个好处。
It's a performance optimization. As a result of this functionality, which of these two function calls do you think is faster?
I'll give you a hint. Here's the disassembly (see http://docs.python.org/library/dis.html):
#
1#
2As you can see, there is a performance benefit when using immutable default arguments. This can make a difference if it's a frequently called function or the default argument takes a long time to construct. Also, bear in mind that Python isn't C. In C you have constants that are pretty much free. In Python you don't have this benefit.
如果您考虑以下内容:
(2)的作用已在此线程中得到了广泛覆盖。 (1)可能是引起因素的惊讶,因为这种行为在来自其他语言时并不是“直观的”。
(1)在python 类上的教程。试图将值分配给只读类属性:
回顾原始示例,然后考虑以上几点:
foo
是一个对象,a
是foo
的属性(在foo.func_defs [0] )。由于
a
是列表,因此a
是可变的,因此是foo
的读写属性。当函数实例化时,它将以签名指定为空列表初始化为空列表,并且只要存在函数对象,就可以读取和写作。调用
foo
而无需覆盖默认值,从foo.func_defs
使用该默认值的值。在这种情况下,foo.func_defs [0]
在函数对象的代码范围内用于a
。更改a
更改foo.func_defs [0]
,它是foo
对象的一部分,并且在中的代码执行之间持续存在。 foo
。现在,将其与,因此每次执行函数时都使用函数签名默认值:
服用(1)和(2)(2),可以考虑为什么要实现所需的行为:
foo
函数对象时,foo.func_defs [0]
设置为none
,一个不可变的对象。l
的参数),foo.func_defs [0]
(none
)可在本地范围中作为l
可用。l = []
上,分配不能在foo.func_defs [0]
上成功,因为该属性仅读取。l
,并用于功能调用的其余部分。foo.func_defs [0]
因此,对于foo
的将来的调用,保持不变。This behavior is not surprising if you take the following into consideration:
The role of (2) has been covered extensively in this thread. (1) is likely the astonishment causing factor, as this behavior is not "intuitive" when coming from other languages.
(1) is described in the Python tutorial on classes. In an attempt to assign a value to a read-only class attribute:
Look back to the original example and consider the above points:
Here
foo
is an object anda
is an attribute offoo
(available atfoo.func_defs[0]
). Sincea
is a list,a
is mutable and is thus a read-write attribute offoo
. It is initialized to the empty list as specified by the signature when the function is instantiated, and is available for reading and writing as long as the function object exists.Calling
foo
without overriding a default uses that default's value fromfoo.func_defs
. In this case,foo.func_defs[0]
is used fora
within function object's code scope. Changes toa
changefoo.func_defs[0]
, which is part of thefoo
object and persists between execution of the code infoo
.Now, compare this to the example from the documentation on emulating the default argument behavior of other languages, such that the function signature defaults are used every time the function is executed:
Taking (1) and (2) into account, one can see why this accomplishes the desired behavior:
foo
function object is instantiated,foo.func_defs[0]
is set toNone
, an immutable object.L
in the function call),foo.func_defs[0]
(None
) is available in the local scope asL
.L = []
, the assignment cannot succeed atfoo.func_defs[0]
, because that attribute is read-only.L
is created in the local scope and used for the remainder of the function call.foo.func_defs[0]
thus remains unchanged for future invocations offoo
.