如何为Python实例方法添加代码块装饰器并访问/使用实例变量?
Let's break down what's going wrong here and fix it step by step.
First, the core issue: When you replace a.put with the wrap function returned by Decorate, you're swapping a bound method (the original a.put, which automatically carries self=a) with a plain function. When you call a.put('it is'), Python won't automatically pass a as the first argument to wrap—instead, the string 'it is' becomes that first parameter, which is why you see AttributeError: 'str' object has no attribute 'name': your self variable in the wrapper is actually the string you passed.
Solution 1: Fix the Decorator to Work with Bound Methods
The original a.put is a bound method, and its __self__ attribute directly references the instance a. We can use this to pass the instance to our before/after functions, and re-bind the wrapped function to the instance so it behaves like a normal method:
def Decorate(func, before=None, after=None): # Extract the instance from the original bound method instance = func.__self__ def wrap(*args, **kwargs): if before: # Pass the instance to the before function before(instance) # Call the original bound method (no need to manually pass self) result = func(*args, **kwargs) if after: after(instance) return result # Re-bind the wrap function to the instance to maintain method behavior return wrap.__get__(instance, type(instance)) class Test(): def __init__(self, name): self.name = name def put(self, prefix): print(prefix, self.name) def some_method(self): print(f"Calling instance method from {self.name}") # Define before/after functions that accept the instance def Before(instance): print('before') print(f"Instance name: {instance.name}") instance.some_method() def After(instance): print('after') a = Test('me') a.put = Decorate(a.put, Before, After) a.put('it is')
Running this will output:
before Instance name: me Calling instance method from me it is me after
Solution 2: Make Before/After Feel Like Instance Methods
If you prefer before and after to use self directly (like regular methods), we can wrap them to automatically inject the instance:
def Decorate(func, before=None, after=None): instance = func.__self__ # Wrap before/after to pass the instance as self def wrapped_before(): if before: before(instance) def wrapped_after(): if after: after(instance) def wrap(*args, **kwargs): wrapped_before() result = func(*args, **kwargs) wrapped_after() return result return wrap.__get__(instance, type(instance)) # Now before uses self just like an instance method def Before(self): print('before') print(f"Instance name: {self.name}") self.some_method() # Usage remains the same a.put = Decorate(a.put, Before, After)
Why Your addMethod Works
Your addMethod uses method.__get__(self)—this is exactly how Python creates bound methods. The __get__ method of a function returns a bound method object that automatically passes the instance as the first (self) argument when called. We use the same logic in Solution 1 with wrap.__get__(instance, type(instance)).
Advanced: Robust Class-Based Decorator
For more flexibility, you can implement a decorator class that handles binding automatically via the descriptor protocol:
class InstanceMethodDecorator: def __init__(self, func, before=None, after=None): self.func = func self.before = before self.after = after def __call__(self, *args, **kwargs): instance = self.func.__self__ if self.before: self.before(instance) result = self.func(*args, **kwargs) if self.after: self.after(instance) return result def __get__(self, instance, owner): # Return a decorator bound to the current instance when accessed if instance is None: return self bound_func = self.func.__get__(instance, owner) return InstanceMethodDecorator(bound_func, self.before, self.after) def Decorate(func, before=None, after=None): return InstanceMethodDecorator(func, before, after)
This version works both for decorating individual instance methods (your use case) and for decorating methods at the class level, while keeping each instance's decoration independent.
内容的提问来源于stack exchange,提问作者Ulrich

