Декораторы применяются , когда функция определена ;в классе, когда класс определен.На данный момент еще нет экземпляров!
У вас есть три варианта:
Зарегистрируйте ваши декораторы на уровне класса.Это не так чисто, как может показаться;вы либо должны явно передать дополнительные объекты вашим декораторам (red_rules = set()
, затем @red(red_rules)
, чтобы фабрика декораторов могла затем добавить функцию в нужное место), либо вам нужно использовать какой-то инициализатор класса, чтобы подобрать специально помеченныефункции;Вы можете сделать это с помощью базового класса, который определяет метод класса __init_subclass__
, после чего вы можете перебирать пространство имен и находить эти маркеры (атрибуты, установленные декораторами).
Пусть ваш метод __init__
(или метод __new__
) перебирает все методы в классе и ищет специальные атрибуты, которые декораторы поместили туда.
Декоратору нужно будет только добавить атрибут _rule_name
или аналогичный к декорированным методам, а {getattr(self, name) for for name in dir(self) if getattr(getattr(self, name), '_rule_name', None) == rule_name}
подберет любой метод с правильным именем правила, определенным в rule_name
.
Заставьте ваших декораторов создавать новые объекты дескриптора ;дескрипторы имеют __set_name__()
метод , вызываемый при создании объекта класса.Это дает вам доступ к классу, и, таким образом, вы можете добавить атрибуты к этому классу.
Обратите внимание, что __init_subclass__
и __set_name__
требуют Python 3.6 или новее;вам придется прибегнуть к метаклассу для достижения аналогичной функциональности в более ранних версиях.
Также обратите внимание, что когда вы регистрируете функции на уровне класса, вам необходимо явно свяжите их с function.__get__(self, type(cls))
, чтобы превратить их в методы, или , которые вы можете явно передать в self
при вызове их.Вы можете автоматизировать это, сделав выделенный класс для хранения наборов правил, и сделайте этот класс тоже дескриптором:
import types
from collections.abc import MutableSet
class RulesSet(MutableSet):
def __init__(self, values=(), rules=None, instance=None, owner=None):
self._rules = rules or set() # can be a shared set!
self._instance = instance
self._owner = owner
self |= values
def __repr__(self):
bound = ''
if self._owner is not None:
bound = f', instance={self._instance!r}, owner={self._owner!r}'
rules = ', '.join([repr(v) for v in iter(self)])
return f'{type(self).__name__}({{{rules}}}{bound})'
def __contains__(self, ob):
try:
if ob.__self__ is self._instance or ob.__self__ is self._owner:
# test for the unbound function instead when both are bound, this requires staticmethod and classmethod to be unwrapped!
ob = ob.__func__
return any(ob is getattr(f, '__func__', f) for f in self._rules)
except AttributeError:
# not a method-like object
pass
return ob in self._rules
def __iter__(self):
if self._owner is not None:
return (f.__get__(self._instance, self._owner) for f in self._rules)
return iter(self._rules)
def __len__(self):
return len(self._rules)
def add(self, ob):
while isinstance(ob, Rule):
# remove any rule wrappers
ob = ob._function
assert isinstance(ob, (types.FunctionType, classmethod, staticmethod))
self._rules.add(ob)
def discard(self, ob):
self._rules.discard(ob)
def __get__(self, instance, owner):
# share the set with a new, bound instance.
return type(self)(rules=self._rules, instance=instance, owner=owner)
class Rule:
@classmethod
def make_decorator(cls, rulename):
ruleset_name = f'{rulename}_rules'
def decorator(f):
return cls(f, ruleset_name)
decorator.__name__ = rulename
return decorator
def __init__(self, function, ruleset_name):
self._function = function
self._ruleset_name = ruleset_name
def __get__(self, *args):
# this is mostly here just to make Python call __set_name__
return self._function.__get__(*args)
def __set_name__(self, owner, name):
# register, then replace the name with the original function
# to avoid being a performance bottleneck
ruleset = getattr(owner, self._ruleset_name, None)
if ruleset is None:
ruleset = RulesSet()
setattr(owner, self._ruleset_name, ruleset)
ruleset.add(self)
# transfer controrol to any further rule objects
if isinstance(self._function, Rule):
self._function.__set_name__(owner, name)
else:
setattr(owner, name, self._function)
red = Rule.make_decorator('red')
blue = Rule.make_decorator('blue')
hard = Rule.make_decorator('hard')
soft = Rule.make_decorator('soft')
Затем просто используйте:
class MyClass:
@red
def rule_one(self):
return 1
@blue
@hard
def rule_two(self):
return 2
@hard
def rule_three(self):
return 3
@blue
@soft
def rule_four(self):
return 4
и вы сможете получить доступ к self.red_rules
и т. Д. Как набор со связанными методами:
>>> inst = MyClass()
>>> inst.red_rules
RulesSet({<bound method MyClass.rule_one of <__main__.MyClass object at 0x106fe7550>>}, instance=<__main__.MyClass object at 0x106fe7550>, owner=<class '__main__.MyClass'>)
>>> inst.blue_rules
RulesSet({<bound method MyClass.rule_two of <__main__.MyClass object at 0x106fe7550>>, <bound method MyClass.rule_four of <__main__.MyClass object at 0x106fe7550>>}, instance=<__main__.MyClass object at 0x106fe7550>, owner=<class '__main__.MyClass'>)
>>> inst.hard_rules
RulesSet({<bound method MyClass.rule_three of <__main__.MyClass object at 0x106fe7550>>, <bound method MyClass.rule_two of <__main__.MyClass object at 0x106fe7550>>}, instance=<__main__.MyClass object at 0x106fe7550>, owner=<class '__main__.MyClass'>)
>>> inst.soft_rules
RulesSet({<bound method MyClass.rule_four of <__main__.MyClass object at 0x106fe7550>>}, instance=<__main__.MyClass object at 0x106fe7550>, owner=<class '__main__.MyClass'>)
>>> for rule in inst.hard_rules:
... rule()
...
2
3
Те же правила доступны для класса;обычные функции остаются несвязанными:
>>> MyClass.blue_rules
RulesSet({<function MyClass.rule_two at 0x107077a60>, <function MyClass.rule_four at 0x107077b70>}, instance=None, owner=<class '__main__.MyClass'>)
>>> next(iter(MyClass.blue_rules))
<function MyClass.rule_two at 0x107077a60>
Тестирование содержимого работает, как и ожидалось:
>>> inst.rule_two in inst.hard_rules
True
>>> inst.rule_two in inst.soft_rules
False
>>> MyClass.rule_two in MyClass.hard_rules
True
>>> MyClass.rule_two in inst.hard_rules
True
Вы можете использовать эти правила для регистрации classmethod
и staticmethod
объектов:
>>> class Foo:
... @hard
... @classmethod
... def rule_class(cls):
... return f'rule_class of {cls!r}'
...
>>> Foo.hard_rules
RulesSet({<bound method Foo.rule_class of <class '__main__.Foo'>>}, instance=None, owner=<class '__main__.Foo'>)
>>> next(iter(Foo.hard_rules))()
"rule_class of <class '__main__.Foo'>"
>>> Foo.rule_class in Foo.hard_rules
True