changing local namespace of a function

Bo Peng bpeng at rice.edu
Sat Feb 5 16:16:34 EST 2005


> Exec is slow since compiling the string and calls to globals() use a lot 
> of time.  The last one is most elegant but __getattr__ and __setattr__ 
> are costly. The 'evil hack' solution is good since accessing x and y 
> takes no additional time.

Previous comparison was not completely fair since I could pre-compile 
fun2 and I used indirect __setattr__. Here is the new one:


 >>> import profile
 >>> a = {'x':1, 'y':2}
 >>> N = 100000
 >>> # solution one: use dictionary directly
... def fun1(d):
...   for i in xrange(0,N):
...     d['z'] = d['x'] + d['y']
...
 >>> # solution two: use exec
... def makeFunction(funcStr, name):
...   code = compile(funcStr, name, 'exec')
...   def f(d):
...     exec code in d
...   return f
...
 >>> def fun2(d):
...   myfun = makeFunction('z = x + y', 'myfun')
...   for i in xrange(0,N):
...     myfun(d)
...   del d['__builtins__']
...
... # solution three: update local dictionary
 >>> # Note that locals() is NOT d after update() so
... #   z = x + y
... # does not set z in d
... def fun3(d):
...   exec "locals().update(d)"
...   for i in xrange(0,N):
...     d['z'] = x + y
...
 >>> # solution four: use dict wrapper
... # this makes code easier to write and read
... class wrapdict(object):
...   """Lazy attribute access to dictionary keys.  Will not access
...      keys that are not valid attribute names!"""
...   def __init__(self, mydict):
...     self.__dict__ = mydict
...
... # use wrapper
 >>> def fun4(d):
...   wd = wrapdict(d)
...   for i in xrange(0,N):
...     wd.z = wd.x + wd.y
...
 >>> profile.run('fun1(a)')
          3 function calls in 0.060 CPU seconds

    Ordered by: standard name

    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
         1    0.000    0.000    0.060    0.060 <string>:1(?)
         1    0.000    0.000    0.060    0.060 profile:0(fun1(a))
         0    0.000             0.000          profile:0(profiler)
         1    0.060    0.060    0.060    0.060 python-10176FWs.py:2(fun1)


 >>> profile.run('fun2(a)')
          200004 function calls in 2.130 CPU seconds

    Ordered by: standard name

    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
         1    0.000    0.000    2.130    2.130 <string>:1(?)
    100000    0.520    0.000    0.520    0.000 myfun:1(?)
         1    0.000    0.000    2.130    2.130 profile:0(fun2(a))
         0    0.000             0.000          profile:0(profiler)
         1    0.590    0.590    2.130    2.130 python-10176EqB.py:1(fun2)
         1    0.000    0.000    0.000    0.000 
python-10176Sgy.py:2(makeFunction)
    100000    1.020    0.000    1.540    0.000 python-10176Sgy.py:4(f)


 >>> profile.run('fun3(a)')
          4 function calls (3 primitive calls) in 0.070 CPU seconds

    Ordered by: standard name

    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       2/1    0.000    0.000    0.070    0.070 <string>:1(?)
         1    0.000    0.000    0.070    0.070 profile:0(fun3(a))
         0    0.000             0.000          profile:0(profiler)
         1    0.070    0.070    0.070    0.070 python-10176R0H.py:4(fun3)


 >>> profile.run('fun4(a)')
          4 function calls in 0.100 CPU seconds

    Ordered by: standard name

    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
         1    0.000    0.000    0.100    0.100 <string>:1(?)
         1    0.000    0.000    0.100    0.100 profile:0(fun4(a))
         0    0.000             0.000          profile:0(profiler)
         1    0.000    0.000    0.000    0.000 
python-10176e-N.py:6(__init__)
         1    0.100    0.100    0.100    0.100 python-10176rIU.py:1(fun4)


Since

   d['x'] is fast but cumbersome
   exec "z=x+y' is still slow.
   exec "locals().update(d)" is evil
   d.x is elegant and only a little slower than d['x']

I am announcing the winner of the contest: dictwrap! (applause)

Bo




More information about the Python-list mailing list