Created on 2014-07-02 11:54 by holdenweb, last changed 2014-07-02 14:47 by r.david.murray. This issue is now closed.
|bugreport.py||holdenweb, 2014-07-02 11:54||Demo code showing error|
|msg222094 - (view)||Author: Steve Holden (holdenweb) *||Date: 2014-07-02 11:54|
When repeated use of a nonlocal variable is made (e.g. to define multiple functions in a loop) ideally the closure should reflect the value of the local variable at the time of use. This should at least be explicitly documented if the behavior is considered not to be a bug. The code sample attached shows that the closures produced operate differently inside and outside the enclosing function. Without an explicit nonlocal declaration the closure should not be able to affect the nonlocal variable's value (which anyway hardly makes sense once the enclosing namespace has been destroyed), so I think it's possible to argue that this behavior is a bug, but I'd value comments from experienced developers.
|msg222095 - (view)||Author: R. David Murray (r.david.murray) *||Date: 2014-07-02 12:06|
Yeah, closures can be a bit counter-intuitive. Assuming *I'm* understanding this correctly, the closure captures a pointer to the local variable, not the value of the local variable, and thus keeps it alive. (That is, the namespace is not destroyed until all closures referencing it have gone away.) https://docs.python.org/3/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result
|msg222097 - (view)||Author: Steve Holden (holdenweb) *||Date: 2014-07-02 12:43|
Indeed the issue is that the pointer is to the local variable rather than its value at time of closure defnition. Not being familiar with the way cells are used, I am unsure as to how the closure keeps the whole namespace alive (that would seem to require a frame rather than just a simple cell).
|msg222100 - (view)||Author: R. David Murray (r.david.murray) *||Date: 2014-07-02 13:13|
I forgot that cells were independent objects. You are probably right about it just keeping the cell alive, but I never did finish looking through how that code worked when I did look at it.
|msg222106 - (view)||Author: Steve Holden (holdenweb) *||Date: 2014-07-02 14:03|
I believe (though my belief is untrammeled by anything as useful as knowledge of the code: my diagnostic skills are largely psychic) that the cell essentially takes over the reference from the local namespace of the about-to-terminate lexically surrounding function. This would appear to be a logical time to create closure cells, as there is effectively no need to create them for functions that will be destroyed. So I imagine any remaining function objects accessible from the return expression will be fixed up at that point. This has the rather unpleasant side effect of capturing the value on surrounding function return rather than closure function creation. The behavior exhibited, in my opinion, shows that there would be strong advantages to creating the closures dynamically, even though I can understand that pathological cases might require much work. It might have to be benchmarked before a decision, I suppose. I couldn't say off-hand how many people are dynamically trying to create multiple closures from a single namespace. It seems to me that the principle of least surprise would suggest a change be adopted, but I may be the only one who's surprised. I have documented this issue in more detail on my blog at http://holdenweb.blogspot.co.uk/2014/07/closures-arent-easy.html and will report back if anything of substance emerges. Otherwise I'll just leave this closed. Thanks for your comment and consideration.
|msg222108 - (view)||Author: R. David Murray (r.david.murray) *||Date: 2014-07-02 14:47|
This is a specific instance of the general principle that a python variable is a 'named' location that holds a pointer to an arbitrary python object. The 'name' in this case is the variable name that appears in multiple scopes (which is what triggers the creation of the cell object...I have no idea at what point in the process it is created). To create a *new* cell object at closure creation time (which is essentially what you are advocating if I understand correctly) would, I think, change the semantics of Python's scoping rules. It would mean that the behavior would be different depending on whether or not 'nonlocal' was specified...if it is nonlocal, the behavior *has* to be the current behavior.
|2014-07-02 14:47:31||r.david.murray||set||messages: + msg222108|
|2014-07-02 14:03:37||holdenweb||set||messages: + msg222106|
|2014-07-02 13:13:04||r.david.murray||set||messages: + msg222100|
|2014-07-02 12:43:37||holdenweb||set||messages: + msg222097|
|2014-07-02 12:06:21||r.david.murray||set||status: open -> closed|
nosy: + r.david.murray
messages: + msg222095
resolution: not a bug