Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do something at the beginning & end of methods

Is there an easy way to do something at the beginning and end of each function in a class? I've looked into __getattribute__, but I don't think that I can use it in this situation?

Here's a simplified version of what I'm trying to do:

class Thing():
    def __init__(self):
        self.busy = False

    def func_1(self):
        if self.busy: 
            return None
        self.busy = True
          ...
        self.busy = False

    def func_2(self):
        if self.busy: 
            return None
        self.busy = True
          ...
        self.busy = False
    ...
like image 968
diligar Avatar asked May 13 '17 01:05

diligar


2 Answers

As an alternative to the accepted answer, if you want this decoration to only be applicable for instance methods, you could use __getattribute__.

class Thing(object):
    def __init__(self):
        self.busy = False

    def __getattribute__(self, name):
        attr = object.__getattribute__(self, name)
        if callable(attr) and not name.startswith('_') and attr.__self__ == self:
            attr = decorator(attr)

        return attr

    def func_1(self):
        # instance method will be wrapped by `decorator`
        ...

    @classmethod
    def class_func(cls):
        # class method will not be wrapped by `decorator`
        # when called using `self.`, `cls.` or `Thing.`.
        ...

    @staticmethod
    def static_func():
        # static method will not be wrapped by `decorator`
        # when called using `Thing.`.
        ...
  • This requires object and will not work for old-style classes in Python 2.
  • callable was removed in Python 3.0, but returned in 3.2. Alternatively, isinstance(obj, collections.Callable) can be used.

If you'd like to wrap class methods and static methods differently, you could inherit from a custom type metaclass:

class Meta(type):
    def __getattribute__(*args):
        print("staticmethod or classmethod invoked")
        return type.__getattribute__(*args)


class Thing(object, metaclass=Meta):
    ...
    def __getattribute__(self, name):
        attr = object.__getattribute__(self, name)
        if callable(attr) and not name.startswith('_'):
            if attr.__self__ == self:
                attr = decorator(attr)
            else:
                attr = Meta.__getattribute__(Thing, name)

        return attr

The above metaclass=Meta is Python 3 syntax. In Python 2, it must be defined as:

class Thing(object):
    __metaclass__ = Meta
like image 158
John B Avatar answered Nov 15 '22 23:11

John B


You can use decorators (if you don't know them you can refer to PEP-318):

def decorator(method):
    def decorated_method(self, *args, **kwargs):
        # before the method call
        if self.busy:
            return None
        self.busy = True

        # the actual method call
        result = method(self, *args, **kwargs)  

        # after the method call
        self.busy = False

        return result

    return decorated_method

class Thing():
    def __init__(self):
        self.busy = False

    @decorator
    def func_1(self):
        ...

    @decorator
    def func_2(self):
        ...

You might want to use functools.wraps if you want the decorated method to "look like" the original method. The @decorator is just syntactic sugar, you could also apply the decorator explicitly:

class Thing():
    def __init__(self):
        self.busy = False

    def func_1(self):
        ...

    func_1 = decorator(func_1)  # replace "func_1" with the decorated "func_1"

In case you really want to apply it to all methods you can additionally use a class decorator:

def decorate_all_methods(cls):
    for name, method in cls.__dict__.items():
        if name.startswith('_'):  # don't decorate private functions
            continue 
        setattr(cls, name, decorator(method))
    return cls

@decorate_all_methods
class Thing():
    def __init__(self):
        self.busy = False

    def func_1(self):
        ...

    def func_2(self):
        ...
like image 25
MSeifert Avatar answered Nov 15 '22 22:11

MSeifert