注意::我不是在问常见的python装饰器,而是装饰器设计模式。
我想编写一个能够修改具体组件调用的函数的装饰器,以下代码示例说明了我的问题:
In [2]: class Animal:
...: def sound(self):
...: raise NotImplementedError
...:
...: def speak(self):
...: print(self.sound())
...:
In [3]: class Dog(Animal):
...: def sound(self):
...: return 'woof!'
...:
In [4]: class Bigfy:
...: def __init__(self, animal):
...: self.animal = animal
...:
...: def sound(self):
...: return self.animal.sound().upper()
...:
...: def speak(self):
...: return self.animal.speak()
...:
In [5]: dog = Dog()
...: dog.speak()
...:
woof!
In [6]: big_dog = Bigfy(Dog())
...: big_dog.sound()
...:
Out[6]: 'WOOF!'
In [7]: big_dog.speak()
woof! # I want 'WOOF!' here
我要增强功能的方法是sound
,但是此方法不是由客户端直接调用的,而是由speak
内部调用的,因此sound
上的所有包装都需要没有效果。
是否可以使用装饰器设计模式实现我想要的?如果没有,我应该看看哪种设计模式?
编辑:感谢大家的快速回答,按照@FHTMitchell的模式,我提出了以下解决方案:
In [1]: import inspect
In [2]: class Animal:
...: def sound(self):
...: raise NotImplementedError
...:
...: def speak(self):
...: print(self.sound())
...:
...: # Key change
...: @property
...: def unwrapped(self):
...: return self
...:
In [3]: class Dog(Animal):
...: def sound(self):
...: return 'woof!'
...:
In [4]: class BaseWrapper:
...: def __new__(cls, animal, **kwargs):
...: self = super().__new__(cls)
...: self.__init__(animal, **kwargs)
...:
...: # Automatically points unwrapped methods to last wrapper
...: for attr in dir(animal):
...: # Don't get magic methods
...: if attr.startswith('__') or attr.startswith('old'):
...: continue
...:
...: value = getattr(animal, attr)
...: if inspect.ismethod(value):
...: # Store old method
...: setattr(self, 'old_' + attr, value)
...: # Points to new method
...: setattr(animal.unwrapped, attr, getattr(self, attr))
...:
...: return self
...:
...: def __init__(self, animal):
...: self.animal = animal
...:
...: # Delegate all non-implemented attrs calls to wrapped class
...: def __getattr__(self, name):
...: return getattr(self.animal, name)
...:
...: # Helps with editor auto-completion
...: def __dir__(self):
...: dir_list = super().__dir__()
...: dir_list.extend(self.animal.__dir__())
...:
...: return dir_list
...:
In [5]: class Bigify(BaseWrapper):
...: def sound(self):
...: return self.old_sound().upper()
...:
In [6]: class Angrify(BaseWrapper):
...: def sound(self):
...: return self.old_sound() + '!!!'
...:
In [7]: class Happify(BaseWrapper):
...: def sound(self):
...: return self.old_sound() + ' =)'
...:
In [8]: big_angry_dog = Happify(Angrify(Bigify(Dog())))
...: big_angry_dog.speak()
...:
WOOF!!!! =)
答案 0 :(得分:1)
使用不同的设计模式-使用动态属性:
class Animal:
sound: str = None
big: bool = False
color: str = None
def speak(self) -> str:
if self.sound is None:
raise NotImplemnetedError()
return self.sound.upper() if self.big else self.sound
那你就可以养一只狗了!
class Dog(Animal):
sound = 'woof!'
一个实例可能很大
mydog = Dog()
mydog.big = True
mydog.speak()
# WOOF!
变棕色
mydog.color = 'brown'
mydog.color # 'brown'
如果您确实想编辑实例方法,则可以这样做(使用OP中的模式)
import types
import functools
def bigify(inst: Animal) -> None:
'''Note this modifies the object in place. Implement a `__copy__()`
method and call `copy.copy` on inst and return new inst if you
don't want this behaviour.
'''
old_method = inst.sound # need to store the old one somewhere
@functools.wraps(inst.sound) # preserve __doc__
def inner(self):
return old_method().upper()
inst.sound = types.MethodType(inner, inst)
bigify(dog)
dog.speak() # WOOF!