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