Сделайте что-то в начале и конце методов

Есть ли простой способ сделать что-то в начале и конце каждой функции в классе? Я просмотрел __getattribute__, но не думаю, что могу использовать его в этой ситуации?

Вот упрощенная версия того, что я пытаюсь сделать:

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
    ...

Ответ 1

Вы можете использовать декораторы (если вы не знаете их, вы можете обратиться к 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):
        ...

Возможно, вы захотите использовать functools.wraps, если хотите, чтобы декорированный метод выглядел как оригинальный метод. @decorator - это просто синтаксический сахар, вы также можете применить декоратор явно:

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"

Если вы действительно хотите применить его ко всем методам, вы можете дополнительно использовать декоратор класса:

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):
        ...

Ответ 2

В качестве альтернативы принятому ответу, если вы хотите, чтобы это украшение применимо только для методов экземпляра, вы можете использовать __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.`.
        ...
  • Для этого требуется object и не будет работать для классов старого стиля в Python 2.
  • callable был удален в Python 3.0, но вернулся в 3.2. В качестве альтернативы можно использовать isinstance(obj, collections.Callable).

Если вы хотите по-разному обернуть методы класса и статические методы, вы можете наследовать от пользовательского 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

Вышеупомянутый metaclass=Meta является синтаксисом Python 3. В Python 2 он должен быть определен как:

class Thing(object):
    __metaclass__ = Meta