Functions create closures, loops do not. The variable name fname
is a local variable in proxyfy
. The nested function proxy_func
refers to this local variable. But at the time when the nested function is called, the for-loop
for _, func in inspect.getmembers(target, predicate=inspect.ismethod):
has completed, and the local variable fname
references its last value at the end of the loop, which happens to be 'foo2'
.
So no matter what method you call, each proxy_func
ends up calling foo2
.
To bind different values of fname
to each proxy_func
, you could use a new keyword parameter, bname
with a default value. The default value is bound to the function at definition-time not at the time the function in run. So if use
for bname, func in inspect.getmembers(target, predicate=inspect.ismethod):
and use this bname
as the default value:
def proxy_func(self, bname=bname, *args, **kwargs):
then each proxy_func
will call the appropriate bname
.
So, with minimal changes to your code, you could add a keyword parameter with default value to proxy_func
to remember the current method name:
def proxy(bridge, target):
def proxyfy(cls):
for bname, func in inspect.getmembers(target, predicate=inspect.ismethod):
fname = func.__name__
if fname in cls.__dict__:
print 'ignoring %s.%s' % (cls, fname)
continue
print 'adding %s.%s' % (cls, fname)
def proxy_func(self, bname=bname, *args, **kwargs):
print 'calling %s.%s.%s' % (cls, bridge, bname)
bridge_member = getattr(self, bridge)
return getattr(bridge_member, bname)(*args, **kwargs)
setattr(cls, fname, proxy_func)
return cls
return proxyfy
However, I think using __getattr__
might be easier:
def proxy(bridge):
def proxyfy(cls):
def __getattr__(self, attr):
target = getattr(self, bridge)
if attr.startswith('__') and not attr.endswith('__'):
# unmangle
attr = '_{}{}'.format(type(target).__name__, attr)
return getattr(target, attr)
setattr(cls, '__getattr__', __getattr__)
return cls
return proxyfy
Here's a runnable example:
import inspect
def proxy(bridge, target):
def proxyfy(cls):
for bname, func in inspect.getmembers(target, predicate=inspect.ismethod):
fname = func.__name__
if fname in cls.__dict__:
print 'ignoring %s.%s' % (cls, fname)
continue
print 'adding %s.%s' % (cls, fname)
def proxy_func(self, bname=bname, *args, **kwargs):
print 'calling %s.%s.%s' % (cls, bridge, bname)
bridge_member = getattr(self, bridge)
return getattr(bridge_member, bname)(*args, **kwargs)
setattr(cls, fname, proxy_func)
return cls
return proxyfy
def proxy(bridge):
def proxyfy(cls):
def __getattr__(self, attr):
target = getattr(self, bridge)
if attr.startswith('__') and not attr.endswith('__'):
# unmangle
attr = '_{}{}'.format(type(target).__name__, attr)
return getattr(target, attr)
setattr(cls, '__getattr__', __getattr__)
return cls
return proxyfy
class Base(object):
def __init__(self, i):
self._i = i
def __bar(self):
print 0
def foo(self):
print self._i
def foo2(self):
print 2 * self._i
# @proxy('_proxy', Base)
@proxy('_proxy')
class Delegate(object):
def __init__(self, base):
self._proxy = base
def foo2(self):
print 4 * self._proxy._i
d = Delegate(Base(1))
d.__bar() # d._proxy.__bar()
d.foo() # d._proxy.foo()
d.foo2() # d.foo2()