【问题标题】:How to wrap every method of a class? [duplicate]如何包装类的每个方法? [复制]
【发布时间】:2012-07-06 03:27:21
【问题描述】:

我想在 python 中包装特定类的每个方法,并且我想通过最小限度地编辑类的代码来做到这一点。我该怎么办?

【问题讨论】:

    标签: python wrapper metaclass


    【解决方案1】:

    Michael Foord 的 Voidspace 博客中的一篇关于什么是元类以及如何在标题为 A Method Decorating Metaclass 的部分中使用它们的条目中描述了一种优雅的方法。稍微简化一下并将其应用于您的情况会导致:

    from functools import wraps
    from types import FunctionType
    
    def wrapper(method):
        @wraps(method)
        def wrapped(*args, **kwargs):
        #   ... <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)  # apply method-wrapping metaclass
        ...
    

    代替:

    class MyClass(object):
        __metaclass__ = MetaClass  # wrap all the methods
        ...
    

    如果需要,可以编写与 Python 2.x 3.x 兼容的代码,但这样做需要使用稍微复杂一点的技术,即动态创建一个新的基类继承所需的元类,从而避免由于两个 Python 版本之间的语法差异而导致的错误。这基本上就是 Benjamin Peterson 的 six 模块的 with_metaclass() 函数所做的。

    from types import FunctionType
    from functools import wraps
    
    def wrapper(method):
        @wraps(method)
        def wrapped(*args, **kwargs):
            print('{!r} executing'.format(method.__name__))
            return method(*args, **kwargs)
        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
    

    【讨论】:

    • 顺便说一句,最近我看到一个非常全面的answerHow to make built-in containers (sets, dicts, lists) thread safe? 的问题进行了描述,其中描述了许多不同的包装方法的方法。我想你可能会觉得它很有趣。
    • 感谢@martineau。是否可以通过具体示例来演示您的答案的 Python3 版本。原因:我无法理解以上哪些是关键词,哪些是实际应用时的替换。我很想用“pandas.DataFrame”类来试试这个。 :)
    • @prismalytics:当然。请参阅我创建的可运行演示,该演示在 Python 2 和 3 中均未更改:wrapping_methods_example.py
    • 谢谢@martineau。我将研究您指向我的示例并将其应用于我的用例。非常感谢(在整个节目中都投了赞成票)。 =:)
    • 喜欢这个!比覆盖 __getattribute__ 干净得多,并且更容易为用户记录。
    【解决方案2】:

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

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

    如果你有课,例如

    class Test( ):
        def fire( self ):
            return True
        def fire2( self ):
            return True
    

    和一个包装器

    def wrapper( fn ):
        def result( *args, **kwargs ):
            print 'TEST'
            return fn( *args, **kwargs )
        return result
    

    然后调用

    wrap_methods( Test, wrapper )
    

    会将wrapper 应用于Test 类中定义的所有 方法。 谨慎使用!实际上,根本不要使用它!

    【讨论】:

    【解决方案3】:

    如果需要大量修改默认类行为,那么 MetaClasses 就是要走的路。这是另一种方法。

    如果您的用例仅限于包装类的实例方法,您可以尝试覆盖 __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
    

    确保在创建包装器时使用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
    

    【讨论】:

    • 我认为值得指出的是,这种方法(重新)包装所有方法每次调用时,这比包装只完成一次需要更多的开销并成为类的一部分,就像使用元类或类装饰器一样。当然,如果只是为了调试目的,这种额外的开销可能是完全可以接受的。
    • @martineau:非常有效的观点。我还应该提到我一直回避 MetaClasses(对我来说这似乎是一个非常脆弱的空间),直到现在。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-14
    • 2021-10-24
    • 1970-01-01
    • 1970-01-01
    • 2012-01-26
    • 2010-11-28
    相关资源
    最近更新 更多