如何装饰子类中的所有继承方法

时间:2019-03-06 00:45:22

标签: python python-3.x decorator

class Reader:
    def __init__(self):
        pass

    def fetch_page(self):
        with open('/dev/blockingdevice/mypage.txt') as f:
            return f.read()

    def fetch_another_page(self):
        with open('/dev/blockingdevice/another_mypage.txt') as f:
            return f.read()   

class Wrapper(Reader):
    def __init__(self):
        super().__init__()

    def sanity_check(func):
        def wrapper():
            txt = func()
            if 'banned_word' in txt:
                raise Exception('Device has banned word on it!')
        return wrapper

    @sanity_check
    <how to automatically put this decorator on each function of base class? >

w = Wrapper()
w.fetch_page()
w.fetch_another_page()

sanity_check类的实例上调用wrapperfetch_page时,如何确保fetch_another_page的{​​{1}}是自动运行的? / p>

3 个答案:

答案 0 :(得分:2)

如果使用python3.6或更高版本,则可以使用__init_subclass__

完成此操作

简单的实现:(实际上,您可能想要一个注册表和functools.wraps等):

class Reader:
    def __init_subclass__(cls):
        cls.fetch_page = cls.sanity_check(cls.fetch_page)
        cls.fetch_another_page = cls.sanity_check(cls.fetch_another_page)

    def fetch_page(self):
        return 'banned_word'

    def fetch_another_page(self):
        return 'not a banned word'

class Wrapper(Reader):
    def sanity_check(func):
        def wrapper(*args, **kw):
            txt = func(*args, **kw)
            if 'banned_word' in txt:
                raise Exception('Device has banned word on it!')
            return txt
        return wrapper


演示:

In [55]: w = Wrapper()

In [56]: w.fetch_another_page()
Out[56]: 'not a banned word'

In [57]: w.fetch_page()
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-57-4bb80bcb068e> in <module>()
----> 1 w.fetch_page()
...

Exception: Device has banned word on it!

编辑:如果无法更改基类,则可以子类化并创建一个Adapter类:

class Reader:

    def fetch_page(self):
        return 'banned_word'

    def fetch_another_page(self):
        return 'not a banned word'

class ReadAdapter(Reader):
    def __init_subclass__(cls):
        cls.fetch_page = cls.sanity_check(cls.fetch_page)
        cls.fetch_another_page = cls.sanity_check(cls.fetch_another_page)

class Wrapper(ReadAdapter):
    def sanity_check(func):
        def wrapper(*args, **kw):
            txt = func(*args, **kw)
            if 'banned_word' in txt:
                raise Exception('Device has banned word on it!')
            return txt
        return wrapper

应提供相同的结果。

答案 1 :(得分:1)

Wrapper子类中没有简单的方法来执行所需的操作。您要么需要命名要用装饰器包装的基类的每个方法,要么在创建Wrapper类之后修改它(也许使用类装饰器),或者需要重新设计基类以提供帮助你出去

一个相对简单的重新设计是用装饰器装饰基类方法,使它们始终调用“验证器”方法。在基类中,验证器可以是无操作者,但子类可以覆盖它以执行您想要的任何操作:

class Base:
    def sanity_check(func):
        def wrapper(self, *args, **kwargs):
            return self.validator(func(self, *args, **kwargs))
        return wrapper

    def validator(self, results):   # this validator accepts everything
        return results

    @sanity_check
    def foo(self):
        return "foo"

    @sanity_check
    def bar(self):
        return "bar"

class Derived(Base):
    def validator(self, results):   # this one doesn't like "bar"
        if results == "bar":
            raise Exception("I don't like bar")
        return results

obj = Derived()
obj.foo() # works
obj.bar() # fails to validate

答案 2 :(得分:0)

这是我的解决方案:

class SubClass(Base):
    def __init__(self, *args, **argv):
        super().__init__(*args, **argv)

        for attr_name in Base.__dict__:
            attr = getattr(self, attr_name)
            if callable(attr):
                setattr(self, attr_name, functools.partial(__class__.sanity_check, attr))

    @classmethod
    def sanity_check(func):
        txt = func()
        if 'banned_word' in txt:
            raise Exception('Device has banned word on it!')
        return txt

仅当您要使用sanity_check处理Base中的每个功能时,此功能才起作用。