changing local namespace of a function

Bo Peng bpeng at rice.edu
Sat Feb 5 00:24:08 EST 2005


Thank all for your suggestions. I have tried all methods and compared 
their performance.

 >>> 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 fun2(d):
...   for i in xrange(0,N):
...     exec 'z = x + y' in globals(), d
...
 >>> # 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):
...     object.__setattr__(self, "mydict",mydict)
...   def __getattr__(self, attrname):
...     return self.mydict[attrname]
...   def __setattr__(self, attrname, value):
...     self.mydict[attrname] = value
...
 >>> # 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.070 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.010    0.010    0.070    0.070 profile:0(fun1(a))
         0    0.000             0.000          profile:0(profiler)
         1    0.060    0.060    0.060    0.060 python-4645vcY.py:2(fun1)


 >>> profile.run('fun2(a)')
          100003 function calls (3 primitive calls) in 5.890 CPU seconds

    Ordered by: standard name

    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
  100001/1    0.440    0.000    5.890    5.890 <string>:1(?)
         1    0.000    0.000    5.890    5.890 profile:0(fun2(a))
         0    0.000             0.000          profile:0(profiler)
         1    5.450    5.450    5.890    5.890 python-46458me.py:2(fun2)


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

    Ordered by: standard name

    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       2/1    0.000    0.000    0.060    0.060 <string>:1(?)
         1    0.000    0.000    0.060    0.060 profile:0(fun3(a))
         0    0.000             0.000          profile:0(profiler)
         1    0.060    0.060    0.060    0.060 python-4645Jxk.py:5(fun3)


 >>> profile.run('fun4(a)')
          300004 function calls in 3.910 CPU seconds

    Ordered by: standard name

    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
         1    0.000    0.000    3.910    3.910 <string>:1(?)
         1    0.000    0.000    3.910    3.910 profile:0(fun4(a))
         0    0.000             0.000          profile:0(profiler)
    100000    0.530    0.000    0.530    0.000 
python-4645W7q.py:10(__setattr__)
         1    0.000    0.000    0.000    0.000 python-4645W7q.py:6(__init__)
    200000    0.960    0.000    0.960    0.000 
python-4645W7q.py:8(__getattr__)
         1    2.420    2.420    3.910    3.910 python-4645jFx.py:1(fun4)

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.

I guess I will go with solution 3. It is evil but it is most close to my 
original intention. It leads to most readable code (except for the first 
line to do the magic and the last line to return result) and fastest 
performance.

Thank again for everyone's help. I have learned a lot from the posts, 
especially the wrapdict class.

Bo



More information about the Python-list mailing list