Как я могу украсить все унаследованные методы в подклассе

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's wrapper запускался автоматически при вызове fetch_page и fetch_another_page в экземпляре класса Wrapper?

Я только что отредактировал вопрос, чтобы использовать более значимую терминологию для того, о чем вы спрашиваете. Контекстные менеджеры не имеют ничего общего с вашим вопросом, за исключением незначительного (вы используете файлы в качестве контекстных менеджеров в Reader). Я думаю, вы хотели термин decorator вместо этого. Если я слишком сильно изменился и вопрос не соответствует тому, что вы хотите знать, пожалуйста, редактировать вопрос самостоятельно, чтобы исправить мои недоразумения.

Blckknght 06.03.2019 06:51
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
1
358
3

Ответы 3

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

Если вы используете 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!

Обновлено: если вы не можете изменить базовый класс, вы можете создать подкласс и создать класс адаптера:

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

Должен дать тот же результат.

Похоже, отличное решение. Единственная проблема в том, что для этого требуется, чтобы мы могли редактировать базовый класс. Будет сложно, если базовый класс находится в какой-то внешней библиотеке.

hasanatkazmi 07.03.2019 03:10

@hasanatkazmi вы можете подклассировать класс адаптера. Добавил еще один пример.

salparadise 07.03.2019 06:48

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

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.

Другие вопросы по теме