Как я могу украсить все унаследованные методы в подклассе - PullRequest
2 голосов
/ 06 марта 2019
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 wrapper был запущен автоматически при вызове fetch_page и fetch_another_page в экземпляре класса Wrapper?

Ответы [ 3 ]

2 голосов
/ 06 марта 2019

Если вы используете 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 голос
/ 06 марта 2019

Нет простого способа сделать то, что вы хотите, из подкласса 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
0 голосов
/ 07 марта 2019

Вот мое решение для этого:

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.

...