I found out a "strange" behavior of when adding methods dynamically to a class. When I do it one by one (non iteratively) everything works fine [Sample 1] but when I try to loop the task then it seems the all the methods points to the body of the last added method [Sample 2.A]. In a further attempt [Sample 2.B] I end up again to the same "strange" result.
Could you help me find out what is the origin of such behavior and how to fix it?
I am using python 3.9.6
Here the code samples:
Test functions
def a(): print('a()')
def a1(): print('a1()')
def a2(p='p'): print('a2({})'.format(p))
Sample 1: one by one approach (correct behavior)
class Test1(object): pass
setattr(Test1, a.__name__, lambda self, *args, **kwargs: a(*args, **kwargs))
setattr(Test1, a1.__name__, lambda self, *args, **kwargs: a1(*args, **kwargs))
setattr(Test1, a2.__name__, lambda self, *args, **kwargs: a2(*args, **kwargs))
Test1().a()
Test1().a1()
Test1().a2()
Output
a()
a1()
a2(p)
Sample 2.A: "strange" behavior - iterative approach
class Test2(object): pass
for f in [a, a1, a2]:
setattr(Test2, f.__name__, lambda self, *args, **kwargs: f(*args, **kwargs))
# print(f.__name__, hasattr(Test2, f.__name__)) # debug: correct output
# eval("Test2().{}()".format(f.__name__)) # debug: correct output
Test2().a()
Test2().a1()
Test2().a2()
print(dir(Test2)) # the methods have right signature but same body!
Output
a2(p)
a2(p)
a2(p)
[..., 'a', 'a1', 'a2']
Sample 2.B: "strange" behavior - factory approach
Test3 = type('Test3', (object,), {f.__name__: lambda self, *args, **kwargs: f(*args, **kwargs) for f in [a, a1, a2]})
Test3().a()
Test3().a1()
Test3().a2()
print(dir(Test3)) # the methods have right signature but same body!
Output
a2(p)
a2(p)
a2(p)
[..., 'a', 'a1', 'a2']
UPDATE
using an auxiliary function for the function-method binding is enough to avoid scoping problem.
class TestClass:
pass
def func2method(func):
# auxiliary function: needed to avoid for-loop scope problems
return lambda self, *args, **kwargs: func(*args, **kwargs) # <- the extra parameter is needed for the binding with the class
# binding step
for f in (a, a1, a2):
setattr(TestClass, f.__name__, func2method(f))
t = TestClass()
t.a() # a()
t.a1() # a1()
t.a2() # a2(p)
t.a() # a()
print(t.a) # <bound method func2method.<locals>.<lambda> of <__main__.TestClass object at 0x7ff4df787a30>>
Thanks to the considerations of Chris Doyle I come out with the solution of the problems (at least the syntactical side).
Solution of Sample 2.A: "strange" behavior - iterative approach
class Test2(object): pass
for f in [a, a1, a2]:
setattr(Test2, f.__name__, \
eval('lambda self,*args, **kwargs: {}(*args, **kwargs)'.format(f.__name__))) # ok
Test2().a()
Test2().a1()
Test2().a2()
Output
a()
a1()
a2(p)
Remarks
eval
acts on the full lambda
expression, if it were only the return value, eval("f(*args, **kwargs)"
, then same problem as beforeeval
-free solution, pass the the iteration variable, f
, as a key-value parameter of the anonymous function
setattr(Test2, f.__name__, lambda self, f=f, *args, **kwargs: f(*args, **kwargs))
Solution of Sample 2.B: "strange" behavior - factory approach
d = {f.__name__: eval('lambda self, *args, **kwargs: f(*args, **kwargs)', dict(f=f, )) for f in [a, a1, a2]} # ok
Test3 = type('Test3', (object,), d)
Test3().a()
Test3().a1()
Test3().a2()
Output
a()
a1()
a2(p)
Remarks
for an eval
-free solution it holds the same argumentation as above with
d = {f.__name__: lambda self, f=f, *args, **kwargs: f(*args, **kwargs) for f in [a, a1, a2]}
I guess the origin of the problem is some kind of conflict with the nested scopes. If someone want to add some remarks on this point then the question can be considered solved.
I am also open to different solution of course... maybe using nonlocal
keyword or even more exotic suffs:)