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

rjk*_*lan 24 python metaclass wrapper

我想在python中包装特定类的每个方法,并且我想通过最低限度地编辑类的代码来实现.我该怎么办呢?

mar*_*eau 21

迈克尔福德的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 ...
Run Code Online (Sandbox Code Playgroud)

在Python中,函数/方法装饰器只是函数包装器加上一些语法糖,使它们易于使用(而且更漂亮).

Python 3兼容性更新

前面的代码使用Python 2.x元类语法,需要进行翻译以便在Python 3.x中使用,但是它将不再适用于以前的版本.这意味着它需要使用:

class MyBase(metaclass=MetaClass)
    ...
Run Code Online (Sandbox Code Playgroud)

代替:

class MyBase(object): 
    __metaclass__ = MetaClass"
    ...
Run Code Online (Sandbox Code Playgroud)

如果需要,可以编写兼容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
Run Code Online (Sandbox Code Playgroud)

  • 顺便说一句,最近我看到了一个非常全面的[answer](http://stackoverflow.com/a/13618333/355230)问题_如何使内置容器(集合,字典,列表)线程安全?_描述了很多包装方法的不同方式。我认为您可能会发现它很有趣。 (2认同)

fre*_*ish 8

你的意思是以编程方式为类的方法设置包装器?嗯,这可能是一个非常糟糕的做法,但是你可以这样做:

def wrap_methods( cls, wrapper ):
    for key, value in cls.__dict__.items( ):
        if hasattr( value, '__call__' ):
            setattr( cls, key, wrapper( value ) )
Run Code Online (Sandbox Code Playgroud)

例如,如果你有课

class Test( ):
    def fire( self ):
        return True
    def fire2( self ):
        return True
Run Code Online (Sandbox Code Playgroud)

和一个包装

def wrapper( fn ):
    def result( *args, **kwargs ):
        print 'TEST'
        return fn( *args, **kwargs )
    return result
Run Code Online (Sandbox Code Playgroud)

然后打电话

wrap_methods( Test, wrapper )
Run Code Online (Sandbox Code Playgroud)

将适用wrapper所有在类中定义的方法Test.谨慎使用!实际上,根本不要使用它!


far*_*der 6

如果需要广泛修改默认类行为,则元类是最佳选择。这是另一种方法。

如果您的用例仅限于包装类的实例方法,您可以尝试覆盖__getattribute__魔术方法。

from functools import wraps
def wrapper(func):
    @wraps(func)
    def wrapped(*args, **kwargs):
        print "Inside Wrapper. calling method %s now..."%(func.__name__)
        return func(*args, **kwargs)
    return wrapped
Run Code Online (Sandbox Code Playgroud)

确保functools.wraps在创建包装器时使用,如果包装器用于调试,则更是如此,因为它提供了合理的 TraceBacks。

import types
class MyClass(object): # works only for new-style classes
    def method1(self):
        return "Inside method1"
    def __getattribute__(self, name):
        attr = super(MyClass, self).__getattribute__(name)
        if type(attr) == types.MethodType:
            attr = wrapper(attr)
        return attr
Run Code Online (Sandbox Code Playgroud)

  • 我认为值得指出的是,这种方法(重新)包装所有方法_每次调用它们时_,这比只进行一次包装并成为类的一部分(可以使用元类完成)相比需要更多的开销或类装饰器。当然,如果只是为了调试目的,这种额外的开销可能是完全可以接受的。 (4认同)