python - Decorator/Wrapper增强方法内部调用

标签 python oop design-patterns decorator

注意:我问的不是常见的 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内部调用的,所以所有的wrap on 声音无效。

是否可以使用装饰器设计模式来实现我想要的?如果不是,我应该看看什么设计模式?


编辑:感谢大家的快速回答,按照@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!!!! =)

最佳答案

简单方法

使用不同的设计模式——使用动态属性:

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!

关于python - Decorator/Wrapper增强方法内部调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50931196/

相关文章:

python - 从消息中获取图片

java - 困惑 : Abstraction and encapsulation are the same?

python - 正则表达式 : How do I find a sub-string that is between two regular expression matches?

python - 为什么 'append' 会改变列表中的所有元素?

c# - 为什么静态类不能有非静态方法和变量?

javascript - 这种隐藏类的方法叫什么?

c++ - TreeView 中的分层数据和 TreeView 更新技术

C++:路由到成员函数的静态函数包装器?

python - 允许远程访问 Elasticsearch

java - 通过创建依赖于特定条件的对象来避免 If-else 代码异味