Consider this loop:
for name, func in inspect.getmembers(targetCls, inspect.ismethod): def wrapper(*args, **kwargs): print ("Start debug support for %s.%s()" % (targetCls.__name__, name))
When wrapper is eventually called, it searches for the value of name . Not finding it in locals (), it searches for it (and finds it) in the extended for-loop scope. But by then the for-loop over, and name refers to the last value in the loop, i.e. TestMethod2 .
So, both times the shell is called, name is evaluated as TestMethod2 .
The solution is to create an extended scope where name bound to the correct value. This can be done using the closure function with default argument values. The default values ββof the arguments are evaluated and fixed during the definition and are bound to variables with the same name.
def Debug(targetCls): for name, func in inspect.getmembers(targetCls, inspect.ismethod): def closure(name=name,func=func): def wrapper(*args, **kwargs): print ("Start debug support for %s.%s()" % (targetCls.__name__, name)) result = func(*args, **kwargs) return result return wrapper setattr(targetCls, name, closure()) return targetCls
In the comments, eryksun offers an even better solution:
def Debug(targetCls): def closure(name,func): def wrapper(*args, **kwargs): print ("Start debug support for %s.%s()" % (targetCls.__name__, name)); result = func(*args, **kwargs) return result return wrapper for name, func in inspect.getmembers(targetCls, inspect.ismethod): setattr(targetCls, name, closure(name,func)) return targetCls
Now closure needs to be sorted once. Each call to closure(name,func) creates its own function region with even values ββfor name and func .