设置类变量的Python装饰器

4 投票
4 回答
4974 浏览
提问于 2025-04-16 13:54

我有一段代码,可以获取所有在 FooBar 这个类里的函数,以及这些函数在参数消息上支持的正则表达式:

functionList = []

def notify(RegExpression):
    def _notify(function):
        functionList.append((RegExpression, function))

        return function

    return _notify

class FooBar:
    @notify(".*")
    def everything(self, message):
        pass

        @notify("(\w+):.*")
    def reply(self, message):
        pass

for foo in functionList:
    print("%s => %s" % foo)

我想做的事情是,把这些函数和它们的参数放到一个类里面,作为类的一个变量。这样可以避免当像 FooBar 这样的类有多个时出现问题。每个类应该有自己的一份列表。

def notify(RegExpression):
    # ???

class FooBar:
    functionList = []

    @notify(".*")
    def everything(self, message):
        pass

        @notify("(\w+):.*")
    def reply(self, message):
        pass

for foo in FooBar.functionList:
    print("%s => %s" % foo)

那么在 notify() 里面应该放些什么呢?

4 个回答

4

当调用 notify 时,类 Foobar 甚至还没有被创建出来。因此,光靠装饰器是无法完成这个操作的。

你可以给函数加上装饰器,然后在类定义完成后再收集这些函数。你可以使用元类或者像下面这样用类装饰器来实现:

import inspect
def notify(regex):
    def mark( func ):
        func.regex = regex
        return func
    return mark

def collect( cls ):
    cls.functionList=[]
    for name, func in inspect.getmembers(cls, inspect.ismethod):
        if hasattr(func, 'regex'):
            cls.functionList.append(func)
    return cls

@collect
class FooBar(object):

    @notify(".*")
    def everything(self, message):
        pass

    @notify("(\w+):.*")
    def reply(self, message):
        pass

for foo in FooBar.functionList:
     print("%s => %s" % (foo.regex, foo))
4

我还是写了这个,所以干脆把第三种方法也发上来吧。这种方法是用一个元类来收集函数:

def notify(regex):
    def mark( func ):
        func.regex = regex
        return func
    return mark

class RegexBase(object):
    class __metaclass__(type):
        """ creates a list of functions with a `regex` attribute 
            and stores it on the class as `functionList`
        """
        def __new__(cls, name, bases, attr):
            fl = []
            for obj in attr.itervalues():
                    if hasattr(obj, 'regex'):
                        fl.append(obj)
            attr['functionList'] = fl
            return type.__new__(cls, name, bases, attr)


class FooBar(RegexBase):

    @notify(".*")
    def everything(self, message):
        pass

    @notify("(\w+):.*")
    def reply(self, message):
        pass

for foo in FooBar.functionList:
     print("%s => %s" % (foo.regex, foo))

我觉得把所有选项放在一个帖子里挺好的。

5

直接用函数装饰器来实现这个功能是不可能的,因为你需要访问当前正在定义的类,而这个类还不存在。一个解决办法是让装饰器把正则表达式作为方法的一个属性存储起来,并在一个基类中收集这些方法。

def notify(regex):
    def decorate(func):
        func.regex = regex
        return func
    return decorate

class Baz(object):
    @property
    def function_list(self):
        for attr in dir(self):
            obj = getattr(self, attr)
            if callable(obj) and hasattr(obj, "regex"):
                yield obj

class FooBar(Baz):
    @notify(".*")
    def everything(self, message):
        pass

    @notify("(\w+):.*")
    def reply(self, message):
        pass

for foo in FooBar().function_list:
    print("%s => %s" % (foo.regex, foo))

撰写回答