繁体   English   中英

从基类包装派生类方法

[英]Wrapping derived class method from base class

想象一下,我有一个基类和一个派生类,如下所示:

class A:
    def foo(self):
        pass

class B(A):
    def foo(self):
        pass

我希望包装由B实例进行的调用foo调用。 不允许修改 B 的任何部分(我不拥有 B)。

我现在拥有的:

class A:
    def __init__(self):
        fnames = ["foo"]
        for fname in fnames:

            def capture(f):
                def wrapper(*args, **kwargs):
                    print("wrapped")
                    return f(*args, **kwargs)

                return wrapper

            meth = capture(getattr(self, fname))
            bound = meth.__get__(self)
            setattr(self, fname, bound)


class B(A):
    def foo(self):
        print("b")


o = B()
o.foo()

print(o.foo)

这可以按预期工作,但是我担心这在内存方面效率低下。

o.foo<bound method A.__init__.<locals>.capture.<locals>.wrapper of <__main__.B object at 0x10523ffd0>> 似乎我必须为我创建的每个实例支付 2 次关闭的费用。

有一个更好的方法吗? 也许是基于元类的方法?

除非您计划同时运行数千个实例,否则这样做的资源使用量不应该让您担心 - 与正在运行的 Python 应用程序将使用的其他资源相比,它相当小。

只是为了比较:异步协程和任务是一种可以在一个进程中创建数千个的对象,它只是“ok”,会有类似的开销。

但是,由于您可以控制B类,因此有几种方法可以做到这一点,而无需求助于“猴子修补”——这将在 B 创建后就地修改它。 当必须修改他们不控制代码的类时,这通常是唯一的选择。

自动包装该方法,当它从B实例中检索时,以一种懒惰的方式,甚至可以省去这一点 - 并且肯定可以比在基类__init__包装更优雅:

如果您事先知道必须包装的方法,并确定它们是在您控制的类的子类中实现的,则可以通过制作专门的__getattribute__方法来实现:这样,该方法仅在即将获取时才被包装用过的。

from functools import wraps, partial

def _capture(f):  # <- there is no need for this to be inside __getattribute__
                  # unless the wrapper is to call `super()`
    @wraps(f)
    def wrapper(self, *args, **kwargs):
        print("wrapped")
        return f(*args, **kwargs) 
        # ^ "f" is already bound when we retrieve it via super().__getattribute__
        # so, take care not to pass "self" twice. (the snippet in the question
        # body seems to do that)
    return wrapper

class A:
    
    def __getattribute__(self, name):
        fnames = {"foo", }
        attr = super().__getattribute__(name)
        if name in fnames:
            # ^ maybe add aditional checks, like if attr is a method,
            # and if its origin is indeed in a
            # class we want to change the behavior
            attr = partial(_capture(attr), self)  
            # ^ partial with self as the first parameter
            # has the same effect as calling __get__ passing
            # the instance to bind the method
            
        return attr
            
class B(A):
    def foo(self):
        pass

至于在创建 B 时包装foo ,这可以使用更少的资源 - 虽然它可以在元类中完成,但从 Python 3.6 开始, __init_subclass__特殊方法可以处理它,而无需自定义元类。

但是,如果代码可能在class C(B):这将再次覆盖foo :如果方法在基类中使用super()调用foo ,则可以多次调用包装器。 避免包装器中的代码多次运行将需要一些复杂的状态处理(但可以毫无意外地完成)。


from functools import wraps

def _capture(f):
    @wraps(f)
    def wrapper(self, *args, **kwargs):
        print("wrapped")
        return f(self, *args, **kwargs) 
        # ^ "f" is retrieved from the class in __init_subclass__, before being 
        # bound, so "self" is forwarded explicitly
    return wrapper

class A:

    def __init_subclass__(cls, *args, **kw):
        super().__init_subclass__(*args, **kw)
        fnames = {"foo",}
        for name in fnames:
            if name not in cls.__dict__:
                continue
            setattr(cls, name, _capture(getattr(cls, name)))
            # ^no need to juggle with binding the captured method:
            # it will work just as any other method in the class, and
            # `self` will be filled in by the Python runtime itself.
            
            # \/ also, no need to return anything.
        
            
class B(A):
    def foo(self):
        pass

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM