一尘不染

如何包装一个类的每个方法?

python

我想将特定类的每个方法包装在python中,并且希望通过最少地编辑该类的代码来实现。我应该怎么做?


阅读 112

收藏
2020-12-20

共1个答案

一尘不染

Michael
Foord的Voidspace博客的一个条目中介绍了一种优雅的实现方法,该条目在“方法修饰元类的方法”部分中介绍了什么是元类以及如何使用它们。稍微简化一下,然后将其应用于您的情况会导致以下结果:

from types import FunctionType
from functools import wraps

def wrapper(method):
    @wraps(method)
    def wrapped(*args, **kwrds):
    #   ... <do something to/with "method" or the result of calling it>
    return wrapped

class MetaClass(type):
    def __new__(meta, classname, bases, classDict):
        newClassDict = {}
        for attributeName, attribute in classDict.items():
            if isinstance(attribute, FunctionType):
                # replace it with a wrapped version
                attribute = wrapper(attribute)
            newClassDict[attributeName] = attribute
        return type.__new__(meta, classname, bases, newClassDict)

class MyClass(object):
    __metaclass__ = MetaClass  # wrap all the methods
    def method1(self, ...):
        # ...etc ...

在Python中,函数/方法装饰器只是函数包装器和一些语法糖,以使其易于使用(更美观)。

Python 3兼容性更新

先前的代码使用Python 2.x元类语法,需要进行翻译才能在Python 3.x中使用,但是在先前的版本中将不再起作用。这意味着它将需要使用:

class MyClass(metaclass=MetaClass)
    ...

代替:

class MyClass(object): 
    __metaclass__ = MetaClass"
    ...

如果需要,可以编写与Python 2.x
3.x兼容的代码,但是这样做需要使用稍微复杂一些的技术,该技术可以动态创建一个继承所需元类的新基类,从而避免由于两个Python版本之间的语法差异。这基本上就是本杰明·彼得森的六个模块的with_metaclass()功能。

from types import FunctionType
from functools import wraps

def wrapper(method):
    @wraps(method)
    def wrapped(*args, **kwrds):
        print('{!r} executing'.format(method.__name__))
        return method(*args, **kwrds)
    return wrapped


class MetaClass(type):
    def __new__(meta, classname, bases, classDict):
        newClassDict = {}
        for attributeName, attribute in classDict.items():
            if isinstance(attribute, FunctionType):
                # replace it with a wrapped version
                attribute = wrapper(attribute)
            newClassDict[attributeName] = attribute
        return type.__new__(meta, classname, bases, newClassDict)


def with_metaclass(meta):
    """ Create an empty class with the supplied bases and metaclass. """
    return type.__new__(meta, "TempBaseClass", (object,), {})


if __name__ == '__main__':

    # Inherit metaclass from a dynamically-created base class.
    class MyClass(with_metaclass(MetaClass)):
        @staticmethod
        def a_static_method():
            pass

        @classmethod
        def a_class_method(cls):
            pass

        def a_method(self):
            pass

    instance = MyClass()
    instance.a_static_method()  # Not decorated.
    instance.a_class_method()   # Not decorated.
    instance.a_method()         # -> 'a_method' executing
2020-12-20