Local variables initialization

Alex Martelli aleaxit at yahoo.com
Sun Feb 26 19:41:18 EST 2006


Michal Kwiatkowski <ruby at no.spam> wrote:
   ...
> def method(self):
>     var_one = self.attr_one
>     var_two = self.attr_two.another_attr
>     empty_list = []
>     # significant code goes here
   ...
> know, I know ;), but maybe there is a way? I would like to code it that way:
> 
> @init_local_variables
> def method(self):
>     # significant code goes here

Such a decorator would have to do very substantial bytecode rewriting,
to turn all references to the magic names within the body of the method
into local-variable references (and a lot of other things besides).
Check the difference...:

>>> import dis
>>> def f1():
...   x = 23
...   return x
... 
>>> dis.dis(f1)
  2           0 LOAD_CONST               1 (23)
              3 STORE_FAST               0 (x)
  3           6 LOAD_FAST                0 (x)
              9 RETURN_VALUE        
>>> def f2():
...   return x
... 
>>> dis.dis(f2)
  2           0 LOAD_GLOBAL              0 (x)
              3 RETURN_VALUE        

Every access to 'x' within f1 is a LOAD_FAST, because the compiler knows
that x is local; but within f2 it's a different opcode, LOAD_GLOBAL,
because the compiler knows that x ISN'T local (it's not assigned to
within the body) and therefore it guesses it must be local.

The whole function object, as well as the code object it contains, must
be rewritten extensively to add the 'magic' names among the local
variables -- not being assigned-to, they're used in all ways as globals
in the code obejct and function object that the decorator receives.

I would suggest that the issue is hairy enough to apply one of the least
understood points of the "Zen of Python", namely:
"If the implementation is hard to explain, it's a bad idea."

If I absolutely HAD to implement such a "micromacro" system, after
pushing back for all I'm worth and presumably failing, I'd punt and go
for SOURCE manipulation instead (which does in turn give heavy
constraints -- no bytecode-only distribution).  For example, here's a
WAY over-fragile example, intended STRICTLY as such:

import inspect

def manip(f):
    s = inspect.getsourcelines(f)[0]
    delta = len(s[0])-len(s[0].lstrip())
    del s[0]
    s[:] = [x[delta:] for x in s]
    delta = len(s[1])-len(s[1].lstrip())
    s.insert(1, delta*' '+'x=23\n')
    d = {}
    exec ''.join(s) in d
    return d[f.func_name]

class X(object):

    @manip
    def a(self):
        print x

    @manip
    def b(self):
        print x+2

x = X()
x.a()
x.b()


The 'manip' example decorator places many implicit constraints on the
sourcecode of the method that it decorates (single-line def clause, NO
docstring to follow, no use of tabs for indentation but rather only
spaces, etc, etc), which is why it's WAY over-fragile -- but, it does
insert an 'x=23' assignment at the very top, as requested. It can, of
course, be reimplemented in a MUCH more solid way, too (e.g., by using
the Python interface to the AST compiler which seems likely to get
introduced in Python 2.5 -- apparently, a prototype of such a Python
module HAS been already implemented during Pycon, i.e., today).

Personally, I would keep pushing back against this approach even after
I'd gone to the trouble of implementing it more solidly -- in no way is
clarity served by having magic local variables appear out of the blue
(or, rather, the black of black magic).  However, whether something CAN
be done, and whether it SHOULD be done, are separate issues.


Alex



More information about the Python-list mailing list