Skip to content

locals() behaviour differs when tracing is in effect #51332

Description

@andbj
mannequin
BPO 7083
Nosy @gvanrossum, @birkenfeld, @terryjreedy, @pitrou, @nedbat
Superseder
  • bpo-17546: Document the circumstances where the locals() dict get updated
  • Files
  • localstest.py
  • localtest2.py
  • 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 = 'https://github.com/birkenfeld'
    closed_at = <Date 2009-10-08.16:58:24.948>
    created_at = <Date 2009-10-08.13:49:57.354>
    labels = ['extension-modules', 'interpreter-core', 'type-bug', 'docs']
    title = 'locals() behaviour differs when tracing is in effect'
    updated_at = <Date 2013-03-28.02:07:08.144>
    user = 'https://bugs.python.org/andbj'

    bugs.python.org fields:

    activity = <Date 2013-03-28.02:07:08.144>
    actor = 'eric.snow'
    assignee = 'georg.brandl'
    closed = True
    closed_date = <Date 2009-10-08.16:58:24.948>
    closer = 'gvanrossum'
    components = ['Documentation', 'Extension Modules', 'Interpreter Core']
    creation = <Date 2009-10-08.13:49:57.354>
    creator = 'andbj'
    dependencies = []
    files = ['15081', '29597']
    hgrepos = []
    issue_num = 7083
    keywords = []
    message_count = 14.0
    messages = ['93745', '93747', '93750', '93751', '93758', '175498', '175596', '178179', '178283', '178294', '181255', '181298', '185349', '185353']
    nosy_count = 6.0
    nosy_names = ['gvanrossum', 'georg.brandl', 'terry.reedy', 'pitrou', 'techtonik', 'nedbat']
    pr_nums = []
    priority = 'normal'
    resolution = 'rejected'
    stage = None
    status = 'closed'
    superseder = '17546'
    type = 'behavior'
    url = 'https://bugs.python.org/issue7083'
    versions = ['Python 2.7', 'Python 3.3', 'Python 3.4']

    Activity

    1. andbj commented on Oct 8, 2009

      andbjmannequin
      MannequinAuthor

      Running the attached script shows that locals() behaves differently
      depending on whether sys.settrace() is active.

      This does not occur when calling locals() in the global scope, only when
      used inside functions.

    2. added
      interpreter-core(Objects, Python, Grammar, and Parser dirs)
      type-bugAn unexpected behavior, bug, or error
      on Oct 8, 2009
    3. pitrou commented on Oct 8, 2009

      @pitrou
      Member

      The same is thing is true of the frame's f_locals attribute. This
      attribute is a copy of the local variables in the frame, because the
      internal storage of these variables is a raw C array for faster access.
      This copy is only synchronized back when a tracing function returns, so
      as to allow implementing a debugger.

      >>> def f():
      ...   a = 1
      ...   l = sys._getframe().f_locals
      ...   b = 2
      ...   return l
      ... 
      >>> f()
      {'a': 1}

      The above optimization (raw C array for faster access of local
      variables) is not done at the global scope, and therefore locals() at
      that scope give you direct access to the variables' internal store
      (which is, actually, the module's __dict__).

      >>> import __main__
      >>> __main__.__dict__ is locals()
      True
    4. gvanrossum commented on Oct 8, 2009

      @gvanrossum
      Member

      So there's no bug here right? It's even documented.

    5. andbj commented on Oct 8, 2009

      andbjmannequin
      MannequinAuthor

      It's documented that "The contents of this dictionary should not be
      modified; changes may not affect the values of local variables used by
      the interpreter."

      The script does the opposite. Perhaps an addition to the documentation?
      "The contents of this dictionary may not be affected by local variables
      used by the interpreter".

    6. added
      docsDocumentation in the Doc dir
      and removed
      interpreter-core(Objects, Python, Grammar, and Parser dirs)
      on Oct 8, 2009
    7. terryjreedy commented on Oct 8, 2009

      @terryjreedy
      Member

      I think the doc is clear enough if one reads and believes it. The main
      doc is "Update and return a dictionary representing the current local
      symbol table." That pretty clearly says that subsequent changes to the
      local symbol table may make the dict out of date until updated by a
      subsequent call.

    8. added
      interpreter-core(Objects, Python, Grammar, and Parser dirs)
      and removed
      docsDocumentation in the Doc dir
      on Oct 8, 2009
    9. nedbat commented on Nov 13, 2012

      @nedbat
      Member

      ITSM this could use more clarification. It is subtle and baffling. I'm struggling to come up with sentences to make it clearer, though.

    10. nedbat commented on Nov 14, 2012

      @nedbat
      Member

      I wrote about this here: http://nedbatchelder.com/blog/201211/tricky_locals.html A reader suggested this addition to the docs, which I like:

      "Multiple invocations within the scope update and return the same dictionary instance. When a trace function is in effect, this dictionary is updated after every statement."

    11. techtonik commented on Dec 26, 2012

      techtonikmannequin
      Mannequin

      I'm 3rd to vote for reopening this issue to clarify documentation or to fix (read below).

      == Why it is important to fix

      1. First personal user story. Until I saw the localstest.py I couldn't figure out what all locals() definitions are talking about. Dictionary, that is a copy, but the copy is sporadically updated when nobody is using in background. It is a weird dictionary with inconsistent behavior placed at a rather critical part of the language.

      2. Second personal story. I probably run into the same bug in bpo-16781. Probably, because I still don't understand what really happens. It is only a suspicion, and it is bad because with my experience I'm supposed to know what happens, but I never had the time to dig deeper until now.

      3. locals() is referenced in exec/execfile() docs for the explanation of arguments. Magic of locals() multiplied by exec.execfile() gives a next level of headache when you need to resolve this. Add settrace() to the formula to awake a hater in you.

      exec/execfile() are used in Python-based scripting tools (such as SCons), in trace scripts and when Python is embedded as a scripting language. Coding on this level should be easy to make more interesting tools to appear.

      == Fix Idea

      1. Is it possible to rename locals() to _locals(), and leave user visible API function local() for an object that updates itself every time it is accessed?
    12. techtonik commented on Dec 27, 2012

      techtonikmannequin
      Mannequin

      After a day of meditation I've come to the following definition for locals():

      locals() return a dictionary that serves as a cache for current local symbol table. This cache is updated every time locals() is called or when a trace function is active.

    13. terryjreedy commented on Dec 27, 2012

      @terryjreedy
      Member

      I agree now that the doc should be clearer (and this issue reopened). For one thing, the exact CPython behavior is an implementation issue rather than a language requirement. Which is to say, its behavior is allowed but not required. I believe an implementation *could* use dicts for function local namespaces. Synchronization would not then be an issue.

      I will try to work up a concrete suggested replacement.

    14. techtonik commented on Feb 3, 2013

      techtonikmannequin
      Mannequin

      Any progress on that? After a month I am inclined that this behavior should be fixed, not only documented. The correct documentation is:

      NOTE: The variable returned by locals() is sporadically updated by core interpreter.

    15. terryjreedy commented on Feb 3, 2013

      @terryjreedy
      Member

      Anatoly, please stop playing with the headers. It is meaningless and irritating, especially when you set them wrong. The 3.5 choice is for issues that will *not* apply before then. An example is actually making a change warned about in a deprecation warning. 3.2 will soon see its last regular bugfix release. The main purpose of the headers is to help developers develop and apply a patch. The secondary purpose is serve as history and to inform users.

      As I explained in my email to you

      1. The OP meant for this to be a code bug report.
      2. This was properly closed as an invalid bug report. So there will be no code patch. So the headers have no use except as history.
      3. I do believe the doc should be changed, *and* I believe it should be a new issue. If I do open one, I will try to remember to add it here as superseder.
    16. techtonik commented on Mar 27, 2013

      techtonikmannequin
      Mannequin

      Attached localtest2.py where an empty locals() call changes behavior.

      --- localtest.py        Wed Mar 27 19:48:06 2013
      +++ localtest2.py       Wed Mar 27 19:45:19 2013
      @@ -3,6 +3,7 @@
       def X():
           l = locals()
           i = "foo"
      +    locals()
           print("Is 'i' in stored locals()? ", ('i' in l))
       print("Running normally")
    17. terryjreedy commented on Mar 27, 2013

      @terryjreedy
      Member

      Calling locals() updates the dict, just as documented.

    18. transferred this issue fromon Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    Labels

    docsDocumentation in the Doc dirextension-modulesC modules in the Modules dirinterpreter-core(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions