Skip to content

Document the circumstances where the locals() dict get updated #61746

Description

@techtonik
mannequin
BPO 17546
Nosy @birkenfeld, @terryjreedy, @amauryfa, @ncoghlan, @nedbat, @merwok, @bitdancer, @florentx, @ericsnowcurrently, @vadmium
Files
  • locals_doc.patch
  • issue17546.stoneleaf.01.patch
  • locals_doc.03.patch
  • locals_doc.04.patch
  • 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:

    assignee = None
    closed_at = None
    created_at = <Date 2013-03-25.17:20:11.304>
    labels = ['type-feature', '3.7', 'docs']
    title = 'Document the circumstances where the locals() dict get updated'
    updated_at = <Date 2016-12-03.04:44:05.501>
    user = 'https://bugs.python.org/techtonik'

    bugs.python.org fields:

    activity = <Date 2016-12-03.04:44:05.501>
    actor = 'martin.panter'
    assignee = 'docs@python'
    closed = False
    closed_date = None
    closer = None
    components = ['Documentation']
    creation = <Date 2013-03-25.17:20:11.304>
    creator = 'techtonik'
    dependencies = []
    files = ['29614', '37711', '39833', '45735']
    hgrepos = []
    issue_num = 17546
    keywords = ['patch']
    message_count = 42.0
    messages = ['185213', '185215', '185216', '185260', '185276', '185282', '185290', '185320', '185321', '185324', '185325', '185327', '185332', '185335', '185336', '185339', '185350', '185358', '185359', '185399', '185404', '185405', '185440', '185473', '185491', '185544', '185558', '185567', '185575', '185585', '185593', '185601', '185613', '185615', '234049', '234072', '234076', '234078', '245922', '245924', '245942', '282267']
    nosy_count = 13.0
    nosy_names = ['georg.brandl', 'terry.reedy', 'amaury.forgeotdarc', 'ncoghlan', 'techtonik', 'nedbat', 'bgailer', 'eric.araujo', 'r.david.murray', 'flox', 'docs@python', 'eric.snow', 'martin.panter']
    pr_nums = []
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'enhancement'
    url = 'https://bugs.python.org/issue17546'
    versions = ['Python 2.7', 'Python 3.5', 'Python 3.6', 'Python 3.7']

    Activity

    1. techtonik commented on Mar 25, 2013

      techtonikmannequin
      MannequinAuthor

      -locals() returns object that does't walk like a duck.
      +locals() returns object that does't work like a dict.

      Much of the confusion with locals() comes from the fact that returned object is labelled a dict, but this heisendict changes its behavior when you're trying to introspect it. So, to give users a hint that this object is different, I propose to rename it to a different type and supply an appropriate help() for it.

    2. added
      stdlibStandard Library Python modules in the Lib/ directory
      on Mar 25, 2013
    3. ericsnowcurrently commented on Mar 25, 2013

      @ericsnowcurrently
      Member

      The documentation seems pretty clear on the nature of the dict returned by locals(). [1] What documentation are you referring to?

      [1] http://docs.python.org/3.4/library/functions.html#locals

    4. ericsnowcurrently commented on Mar 25, 2013

      @ericsnowcurrently
      Member

      Sorry, didn't mean to change the attributed version. However the 2.7 documentation says the same thing.

    5. techtonik commented on Mar 26, 2013

      techtonikmannequin
      MannequinAuthor
      1. The documentation doesn't say that the content of this dict may change sporadically even if you don't call locals()

      2. I am referring to inline documentation:

          >>> h = locals()
          >>> type(h)
          <class 'dict'>
          >>> help(h)
    6. ericsnowcurrently commented on Mar 26, 2013

      @ericsnowcurrently
      Member

      So the problem you are having is that inside functions the dict returned by locals() does not get updated when the "current local symbol table" changes? Keep in mind that for modules and classes (the other two execution blocks), the dict returned by locals() does get updated.

      This difference reflects that fact that, in CPython at least, the local execution namespace of a function call's stack frame is not stored in a dictionary. When you call locals() in a function it is only making a copy of the frame's fast locals. The fast locals never gets directly exposed except through normal name lookup/binding/deletion. While technically possible, changing this simply isn't worth it.

      The documentation already makes it clear that the dict returned by locals() represents the "current local symbol table" and that you should not expect changes to that dict to be reflected in the actual locals. Are you recommending that it be more clear that the dict may be no more than a snapshot of the locals at the time locals() is called?

    7. techtonik commented on Mar 26, 2013

      techtonikmannequin
      MannequinAuthor

      Under the trace function, the dict is always updated, and that changed a workflow in the program I was debugging leading to heisenbug. But that's a different story. I'd like to concentrate on the reasons to rename locals() result type from 'dict' to 'livedict' (or other internal type with well-defined behavior).

      dict() returned by locals() is sporadically updated. The problem is not with when it is updated, or how it is updated. The problem is that the fact that it is updated is not indicated in any way. And the problem that comes from this problem is that people don't realize that this dict may change its contents. As a result people become confused with Python.

      The documentation is also ambiguous. Those people who don't know about self-updating behavior (I was) read "represents" like a "copy of symbol table", not like a "internally updated view of symbol table".

      From the OOP point of view, the object doesn't behave like a normal dict, and it will be less confusing if it is renamed.

      offtopic: "local execution namespace of a function call's stack frame" sounds cryptic - I am afraid that without visualization of local/global/namespace/scope/frame I am unable to understand that right now.

      offtopic2: I also don't get "you should not expect changes to that dict to be reflected in the actual locals". I reads as if in some cases modifying the dict will change the locals. I thought that it is impossible as it is just a copy.

    8. ericsnowcurrently commented on Mar 26, 2013

      @ericsnowcurrently
      Member

      Thanks for the details, Anatoly. I am surprised by the behavior you've described. It may be that functions don't use fast locals when tracing is turned on. I'll have to check. If that's the case, I think the documentation for locals() should be improved. Either way, I agree that inconsistent (and undocumented) behavior is a headache. However, changing the type of the object returned by locals() is going to take a lot more justification. A doc update should be sufficient and a much easier sell.

      Here are some further questions:

      1. the dynamic update you described happens in function bodies (i.e. stack frames)?
      2. does that behavior happen when you are not using a tracing function?

      Also, if you have a minute, throw up a patch that reproduces the behavior you're talking about. That will help get this resolved faster.

      Keep in mind that locals() always returns a normal dict. Just like any other dict, other code that has a reference to that "locals" dict can interact with it, which is what you have described. In this case, the interpreter is doing so. The only case where I find that surprising is where a stack frame is using fast locals, which is what function calls normally do.

    9. added
      docsDocumentation in the Doc dir
      and removed
      stdlibStandard Library Python modules in the Lib/ directory
      on Mar 27, 2013
    10. changed the title [-]rename type returned by locals() to livedict[/-] [+]Document the circumstances where the locals() dict gets updated[/+] on Mar 27, 2013
    11. techtonik commented on Mar 27, 2013

      techtonikmannequin
      MannequinAuthor

      Raymond, could you please get the title back? You unintentionally hijacked the issue. The "Document the circumstances where the locals() dict gets updated" is not a describing title for this issue - it is one of the possible action items. I'd prefer to see a separate issue to document the stuff that "blocks" this one. It is quite evident that this action is required, but IMHO there is not enough evidence to conclude that it will be sufficient to close this ticket.

      Eric, I need more time to answer your questions.

    12. amauryfa commented on Mar 27, 2013

      @amauryfa
      Contributor

      The previous title, "rename type returned by locals() to livedict" did not describe the reality, since locals() returns a regular dict.
      [Would you call x.__dict__ a livedict?]

      So either this issue should be closed as invalid, because it's based on incorrect understandings of Python internals; or we could improve documentation about locals() in order to remove surprising behavior and (real) confusion in programmers' minds.

    13. 24 remaining items

    14. bitdancer commented on Mar 31, 2013

      @bitdancer
      Member

      Hmm. Perhaps the last sentence could be "... because changes to the local dict propagating to the local namespace cannot be relied upon to either happen or not happen". That would make it less redundant, since it would essentially be referencing the previous statement in the specific case of the consequences of modification.

      The original included the caution against modifying it, and I think it is valid because of the inconsistent behavior. Perhaps it could be weakened to "it is not a good idea to modify"?

    15. changed the title [-]Document the circumstances where the locals() dict gets updated[/-] [+]Document the circumstances where the locals() dict get updated[/+] on Mar 31, 2013
    16. techtonik commented on Mar 31, 2013

      techtonikmannequin
      MannequinAuthor

      ... cannot be relied upon to either happen or not happen...

      IMHO this phrase is from Advanced English course.

      The original included the caution against modifying it, and I think it is
      valid because of the inconsistent behavior.
      Perhaps it could be weakened to "it is not a good idea to modify"?

      Ambiguity already adds fear and uncertainty. Just explaining the
      consequences would be good style for the technical doc.

    17. ethanfurman commented on Jan 15, 2015

      @ethanfurman
      Member

      Combined the second and last lines, discarded duplication.

    18. bitdancer commented on Jan 15, 2015

      @bitdancer
      Member

      Your formulation is more concise, thank you.

      I suggest dropping the word 'additionally'. Also, "how it does" would be better phrased as "how it changes", I think. (It really should be "whether and how it changes", but in deference to Anatoly's 'advanced English' comment I'm willing to let that imprecision slide).

    19. vadmium commented on Jan 15, 2015

      @vadmium
      Member

      What about instead of

      '''
      Whether changes to one are reflected in the other after the call returns is undefined; additionally, the dictionary may change unpredictably after the call, and how it does is implementation-specific.
      '''

      substitue this wording:

      '''
      Whether changes to one are reflected in the other after the call returns, and when such updates occur, is undefined and implementation-specific.
      '''

      The old wording seems under-specified. It would allow a function call, garbage collection, etc, to clobber the dictionary, say overwriting with another function’s locals(), before you get a chance to work with the dictionary.

    20. bitdancer commented on Jan 15, 2015

      @bitdancer
      Member

      Yeah, the question of thread-safety in regards to what we are talking about here also occurred to me. That is, the wording makes one wonder if locals is thread safe or not. I don't see your suggested wording as making it clearer, though.

      The problem is that it *is* underspecified.

      So I think the correct description of the current under-specification is that locals() returns a copy of the current locals namespace. If you modify the thing returned by locals it may or may not update the local namespace. If you modify the local namespace (by doing x = y or its equivalents) the change may or may not be reflected in the object that was returned by locals().

      Now, how do we boil that down? Or do we just say it more or less that way?

    21. vadmium commented on Jun 29, 2015

      @vadmium
      Member

      Here is another attempt with different words:

      '''
      .. note::
      The dictionary returned by :func:`locals` is an accurate snapshot of the local namespace at the time it is called. If the namespace changes after the call, the dictionary may become out of date, but it may also automatically update at any time. The contents of the dictionary should not be modified by the user; it is undefined whether such changes affect the namespace or not.
      '''

    22. ncoghlan commented on Jun 29, 2015

      @ncoghlan
      Contributor

      I mostly like Martin's suggested wording, but would also note that I filed bpo-17960 to tighten up the requirements for when we expect assigning to locals() to work.

      To save folks reading the whole referenced email, I think it would be worth defining that modifying the namespace returned by locals() will affect the runtime namespace at module and class scope and when using exec, but will have no effect at function scope (as locals() returns a copy of the namespace in that case).

    23. vadmium commented on Jun 29, 2015

      @vadmium
      Member

      I quickly scanned through the email thread from bpo-17960. I guess it makes sense to specify that locals() can be used to directly get a class’s namespace. Probably doesn’t hurt to say locals() is equivalent to globals() at module level, although this seems like a fairly redundant feature.

      Here is locals_doc.03.patch, which uses my wording for function namespaces, and also adds more for class and global namespaces, as suggested by Nick.

    24. vadmium commented on Dec 3, 2016

      @vadmium
      Member

      Some minor tweaks to my earlier patch:

      • list comprehension → comprehension
      • time it is called → time of the call
    25. transferred this issue fromon Apr 10, 2022
    26. ncoghlan commented on May 6, 2024

      @ncoghlan
      Contributor

      PEP 667 has resolved this issue for Python 3.13b1

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      3.7 (EOL)end of lifedocsDocumentation in the Doc dirtype-featureA feature request or enhancement

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions