Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python decorator which set class variable

I have a code which get list of all functions in FooBar and a regular expression the function supports on its parameter message:

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)

I would like to do something like that but put the list of functions and their parameters into class as a class variable. It would prevent problems when more classes like FooBar exist. Each class should have its own list.

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)

What put into notify()?

like image 681
izidor Avatar asked Dec 07 '22 22:12

izidor


2 Answers

Doing this directly with a function decorator is impossible, since you would need to access the class which is currently being defined, and this class does not exist yet. One solution would be to make the decorator just store the regex as an attribute of the method and have functionality collecting these methods on a base class:

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))
like image 76
Sven Marnach Avatar answered Dec 21 '22 20:12

Sven Marnach


When notify is called the class Foobar does not even exist yet. Therefore, you can't do with just the decorator.

What you can do is mark the functions with a decorator and collect them after the class has been defined. You can do it with a metaclass or with a class decorator like this:

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))
like image 31
Jochen Ritzel Avatar answered Dec 21 '22 19:12

Jochen Ritzel