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?






Нет простого способа сделать то, что вы хотите, из подкласса 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 вы можете подклассировать класс адаптера. Добавил еще один пример.
Вот мое решение для этого:
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.
Я только что отредактировал вопрос, чтобы использовать более значимую терминологию для того, о чем вы спрашиваете. Контекстные менеджеры не имеют ничего общего с вашим вопросом, за исключением незначительного (вы используете файлы в качестве контекстных менеджеров в
Reader). Я думаю, вы хотели терминdecoratorвместо этого. Если я слишком сильно изменился и вопрос не соответствует тому, что вы хотите знать, пожалуйста, редактировать вопрос самостоятельно, чтобы исправить мои недоразумения.