python / cpython

The Python programming language
https://www.python.org
Other
63.14k stars 30.23k forks source link

Multiple closures accessing the same non-local variable always see the same value #66103

Closed holdenweb closed 10 years ago

holdenweb commented 10 years ago
BPO 21904
Nosy @bitdancer
Files
  • bugreport.py: Demo code showing error
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields: ```python assignee = None closed_at = created_at = labels = ['type-bug', 'invalid'] title = 'Multiple closures accessing the same non-local variable always see the same value' updated_at = user = 'https://bugs.python.org/holdenweb' ``` bugs.python.org fields: ```python activity = actor = 'r.david.murray' assignee = 'none' closed = True closed_date = closer = 'r.david.murray' components = [] creation = creator = 'holdenweb' dependencies = [] files = ['35829'] hgrepos = [] issue_num = 21904 keywords = ['needs review'] message_count = 6.0 messages = ['222094', '222095', '222097', '222100', '222106', '222108'] nosy_count = 2.0 nosy_names = ['holdenweb', 'r.david.murray'] pr_nums = [] priority = 'normal' resolution = 'not a bug' stage = 'resolved' status = 'closed' superseder = None type = 'behavior' url = 'https://bugs.python.org/issue21904' versions = ['Python 2.7', 'Python 3.3'] ```

    holdenweb commented 10 years ago

    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.

    bitdancer commented 10 years ago

    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

    holdenweb commented 10 years ago

    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).

    bitdancer commented 10 years ago

    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.

    holdenweb commented 10 years ago

    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.

    bitdancer commented 10 years ago

    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.