Thursday, 31 January 2013

Python Discovery: Locals() and Globals()

Today I will mess with locals() and globals()

You may have encountered locals() and globals() before.

Here is what they do:

    >>> a = 3
    >>> def b():
    ...     c = 4
    ...     print 'locals', locals()
    ...     print 'globals', globals()
    ...
    >>> b()
    locals {'c': 4}
    globals {'a': 3, 'b': <function b at 0x021984B0>, '__builtins__': <module '__bui
    ltin__' (built-in)>, '__package__': None, '__name__': '__main__', '__doc__': Non
    e}
    >>>

They return a dictionary containing the local or the global scope.

According to this document, "scopes in Python do not nest!". At first it seemed like the python interpreter had an instance of the number two, but I think it actually counts as One local scope, and One extra scope to scan if the variable is not in this local scope.

It seems like manipulating globals() actually manipulates the names you have in your hands, directly. I liked this a lot, and was dumbfounded when I found out about it.

    >>> def b():
    ...     globals()['can_i_inject_a_name_plz'] = 'yes lol'
    ...     print can_i_inject_a_name_plz
    ...
    >>> b()
    yes lol

Yes, I was dumbfounded. I was fascinated. And then I found this:

    >>> def b():
    ...     locals()['injecting'] = 'yes'
    ...     print injecting
    ...
    >>> b()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 3, in b
    NameError: global name 'injecting' is not defined

So I can't inject a name into the local scope. Well, I still have the global scope to play with. I also found that I can do this in the global scope of the interactive interpreter:

    >>> locals()['a'] = 4
    >>> a
    4

It seems like the Python interpreter still has his own secrets.

I decided to try to override variables below the current call stack. Manipulating the variables within other scopes granted me the ultimate power, and made my brain flesh out hundreds of ideas reeking of bad code practise and stuff which very few people, if any, had even used before in python:

    >>> globals()['name'] = 3
    >>> def g():
    ...     name = 6
    ...     print name
    ...
    >>> g()
    6

But no. My ideas crumbled. I can't access or change arbitrary names inside the functions I call. A small disappointment, followed by an "Eureka!" moment:

What if the function asked for it using the global keyword?

    >>> def g():
    ...     global locally_defined
    ...     try:
    ...         locally_defined
    ...     except NameError:
    ...         locally_defined = 'Local value'
    ...     print locally_defined
    ...
    >>> g()
    Local value
    >>>
    >>> globals()['locally_defined'] = 'Crazy value' # monkey patch!
    >>>
    >>> g()
    Crazy value
    >>>
    >>> del globals()['locally_defined'] # unpatch!

Or, even better, with the same effect.

    >>> def f():
    ...     global a
    ...     try:
    ...         a
    ...     except NameError:
    ...         a = 'local_value'
    ...     print a
    ...
    >>>

This is all nice, but what I achieved looks a lot like the intrincate and dark techniques of "argument passing" and "default arguments". I really don't want to lurk there.

Today's discovery was a failure. I learned much, but none of it is actually usable in real code. Sometime later, I will learn something better.

No comments:

Post a Comment