Python 使用基于装饰器的方法注册表初始化

Python 使用基于装饰器的方法注册表初始化,python,class,decorator,Python,Class,Decorator,我有一个类,它有几个方法,每个方法都有特定的属性(从质量上讲)。我希望这些方法在类中的列表中可用,以便可以立即执行。请注意,属性可以互换,因此无法通过使用继承自原始类的其他类来解决这一问题。在一个理想的世界里,它看起来是这样的: class MyClass: def __init__(): red_rules = set() blue_rules = set() hard_rules = set() soft_rules =

我有一个类,它有几个方法,每个方法都有特定的属性(从质量上讲)。我希望这些方法在类中的列表中可用,以便可以立即执行。请注意,属性可以互换,因此无法通过使用继承自原始类的其他类来解决这一问题。在一个理想的世界里,它看起来是这样的:

class MyClass:
    def __init__():
        red_rules = set()
        blue_rules = set()
        hard_rules = set()
        soft_rules = set()

    @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
当类被实例化时,通过组合集合和执行方法,应该很容易执行所有红色和软规则。不过,这方面的装饰器很棘手,因为常规注册装饰器可以填充全局对象,但不能填充class属性:

def red(fn):
    red_rules.add(fn)
    return fn

如何实现这样的功能?

您可以将
集合子类化,并为其提供一个decorator方法:

class MySet(set):
    def register(self, method):
        self.add(method)
        return method

class MyClass:
    red_rules = MySet()
    blue_rules = MySet()
    hard_rules = MySet()
    soft_rules = MySet()

    @red_rules.register
    def rule_one(self):
        return 1

    @blue_rules.register
    @hard_rules.register
    def rule_two(self):
        return 2

    @hard_rules.register
    def rule_three(self):
        return 3

    @blue_rules.register
    @soft_rules.register
    def rule_four(self):
        return 4
或者,如果您发现使用
.register
方法很难看,则始终可以定义
\uuuu调用\uuuu
方法,将集合本身用作装饰器:

class MySet(set):
    def __call__(self, method):
        """Use set as a decorator to add elements to it."""
        self.add(method)
        return method

class MyClass:
    red_rules = MySet()
    ...

    @red_rules
    def rule_one(self):
        return 1

    ...
这看起来更好,但不够明确,所以对于其他合作者(或未来的你自己)来说,可能更难理解这里发生了什么


要调用存储的函数,只需在所需的集合上循环,并作为
self
参数传入实例:

my_instance = MyClass()
for rule in MyClass.red_rules:
    rule(my_instance)
您还可以创建一个实用函数来完成此操作,例如,您可以创建一个
MySet.invoke()
方法:

class MySet(set):
    ...
    def invoke(self, obj):
        for rule in self:
            rule(obj)
现在就打电话:

MyClass.red_rules.invoke(my_instance)
或者你可以让
MyClass
来处理这个问题:

class MyClass:
    ...
    def invoke_rules(self, rules):
        for rule in rules:
            rule(self)
然后在
MyClass
的实例上调用此函数:

my_instance.invoke_rules(MyClass.red_rules)

您可以子类化
set
,并为其提供一个decorator方法:

class MySet(set):
    def register(self, method):
        self.add(method)
        return method

class MyClass:
    red_rules = MySet()
    blue_rules = MySet()
    hard_rules = MySet()
    soft_rules = MySet()

    @red_rules.register
    def rule_one(self):
        return 1

    @blue_rules.register
    @hard_rules.register
    def rule_two(self):
        return 2

    @hard_rules.register
    def rule_three(self):
        return 3

    @blue_rules.register
    @soft_rules.register
    def rule_four(self):
        return 4
或者,如果您发现使用
.register
方法很难看,则始终可以定义
\uuuu调用\uuuu
方法,将集合本身用作装饰器:

class MySet(set):
    def __call__(self, method):
        """Use set as a decorator to add elements to it."""
        self.add(method)
        return method

class MyClass:
    red_rules = MySet()
    ...

    @red_rules
    def rule_one(self):
        return 1

    ...
这看起来更好,但不够明确,所以对于其他合作者(或未来的你自己)来说,可能更难理解这里发生了什么


要调用存储的函数,只需在所需的集合上循环,并作为
self
参数传入实例:

my_instance = MyClass()
for rule in MyClass.red_rules:
    rule(my_instance)
您还可以创建一个实用函数来完成此操作,例如,您可以创建一个
MySet.invoke()
方法:

class MySet(set):
    ...
    def invoke(self, obj):
        for rule in self:
            rule(obj)
现在就打电话:

MyClass.red_rules.invoke(my_instance)
或者你可以让
MyClass
来处理这个问题:

class MyClass:
    ...
    def invoke_rules(self, rules):
        for rule in rules:
            rule(self)
然后在
MyClass
的实例上调用此函数:

my_instance.invoke_rules(MyClass.red_rules)
定义函数时应用修饰符;在一个类中,当该类被定义时。此时此刻还没有实例

您有三种选择:

  • 在类级别注册你的装饰者。这并不像听起来那么干净;您要么必须显式地将其他对象传递给装饰器(
    red\u rules=set()
    ,然后
    @red(red\u rules)
    ,这样装饰器工厂就可以将函数添加到正确的位置),要么必须使用某种类初始值设定器来选取特别标记的函数;您可以使用定义的基类来实现这一点,此时您可以在名称空间上迭代并找到那些标记(装饰器设置的属性)

  • 让你的
    \uuuuu init\uuuuuuuuuuu
    方法(或
    \uuuuuuu new\uuuuuuuuuuuu
    方法)在类上的所有方法上循环,并查找修饰符放在那里的特殊属性

    装饰器只需要为装饰方法添加一个
    \u rule\u name
    或类似的属性,如果getattr(getattr(self,name),“\u rule\u name',None)=rule\u name}
  • 将选择在
    rule\u name
    中定义了正确规则名称的任何方法,那么装饰器将只需要为dir(self)中的名称添加
    {getattr(self,name)

  • 让你的装饰师生产新的;描述符在创建类对象时被调用。这使您可以访问该类,因此可以向该类添加属性

  • 注意
    \uuuu init\u subclass\uuuu
    \uuuu set\u name\uuuuu
    需要Python 3.6或更新版本;要在早期版本中实现类似的功能,您必须求助于

    还要注意的是,当您在类级别注册函数时,您需要显式地将它们与
    函数绑定。
    将它们转换为方法,或者您可以在调用它们时显式地传入
    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\u规则
    等:

    >>> 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
    
    您也可以使用这些规则注册
    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
    
    >>类Foo:
    ...     @坚硬的
    ...     @类方法
    ...     def规则_类(cls):
    ...         返回f'rule_类的{cls!r}'
    ...
    >>>Foo.hard_规则
    规则集({},实例=None,所有者=)
    >>>下一步(国际热核聚变实验堆(Foo.hard_rules))()
    “规则(类别)”
    >>>Foo.rule\u类在Foo.hard\u规则中
    真的
    
    定义函数时应用装饰符;在一个类中,当该类被定义时。此时此刻还没有实例

    您有三种选择:

  • 在类级别注册你的装饰者。这并不像听起来那么干净;您要么必须显式地将其他对象传递给装饰器(
    red\u rules=set()
    ,然后
    @red(red\u rules)
    ,这样装饰器工厂就可以将函数添加到正确的位置),要么必须使用某种类初始值设定器来选取特别标记的函数;您可以使用定义的基类来实现这一点,此时您可以在名称空间上迭代并找到那些标记(装饰器设置的属性)

  • 让你的
    \uuuuu init\uuuuuuuuuuu
    方法(或
    \uuuuuuu new\uuuuuuuuuuuu
    方法)在类上的所有方法上循环,并查找修饰符放在那里的特殊属性

    装饰者只需添加
    \u规则\u名称
    或类似属性即可