Nick Coghlan added another interesting note in response to my last post about creating side-effecting assignments in python. Here’s Nick:
@brandon_rhodes @akaptur You can do better if you're the one controlling the namespace creation (e.g. an importer): https://t.co/gA8LD121cD
— Nick Coghlan (@ncoghlan_dev) June 29, 2013
Let’s look at that gist:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
This is really fun stuff, and nicely illustrates a core feature of python – that namespaces are basically just dictionaries.
So what’s going on here? In this case, I think it’s easiest to work backwards from the end.
Last we have exec code in Madness()
. The exec
keyword in python 21 executes a string as code.
1 2 3 |
|
exec
optionally takes a context in which to execute the code. A dictionary is a perfectly legal context to use:
1 2 |
|
All the code is shown here – b
is not defined elsewhere in the REPL session.
If a dictionary is provided, it’s presumed to contain both the global and the local variables. This dictionary is the only context in which the code will be executed (it won’t check the scope from which the exec
statement was made).
1 2 3 4 5 6 |
|
So without knowing anything about the Madness()
object, we’d expect it to be a dictionary-like thing.
And sure enough, it is:
1 2 |
|
__setitem__
is the standard way to override setting a key:value pair in a dictionary. It works just like you’d expect:
1 2 3 4 |
|
We’re intercepting any attempt to write to the Madness dictionary-like object.
1 2 3 4 |
|
If the value of the key:value pair that we’re setting is an instance of the type
type – that is, if the object in question is a class – then grab that class’s name and set it to the key. (This step will blow up if the key in question isn’t a legal name in the first place.) Then use the parent class (dict
)’s __setitem__
to actually write to the Madness object.
We can execute the code string one line at a time to better see what’s happening.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
(I’m using m.keys()
to examine the state of the namespace instead of just printing m
because the reference to __builtins__
pukes a bunch of tiresome definitions and copyright statements into the REPL. exec
adds a reference to __builtins__
on the execution of any statement.)
We’re now quite close to the original Ruby behavior – with a deeper understanding of python namespaces!
-
All of this works in python 3, too, with slightly different syntax because
exec
is a function, not a keyword. Nick’s gist includes the python 3 version too.↩