简体   繁体   English

从基类包装派生类方法

[英]Wrapping derived class method from base class

Imagine I have a base and a derived class like so:想象一下,我有一个基类和一个派生类,如下所示:

class A:
    def foo(self):
        pass

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

I wish to wrap calls foo calls made by instances of B .我希望包装由B实例进行的调用foo调用。 Modifying any part of B is not allowed (I don't own B).不允许修改 B 的任何部分(我不拥有 B)。

What I have as of now:我现在拥有的:

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)

This works as expected, however I am concerned about this being inefficient memory-wise.这可以按预期工作,但是我担心这在内存方面效率低下。

o.foo is a <bound method A.__init__.<locals>.capture.<locals>.wrapper of <__main__.B object at 0x10523ffd0>> . o.foo<bound method A.__init__.<locals>.capture.<locals>.wrapper of <__main__.B object at 0x10523ffd0>> It would seem that I would have to pay the cost of 2 closures for each instance I create.似乎我必须为我创建的每个实例支付 2 次关闭的费用。

Is there a better way to do this?有一个更好的方法吗? Perhaps a metaclass based approach?也许是基于元类的方法?

Unless you are planning to have several thousands instances of these live at the same time, the resource usage of doing so should not concern you - it is rather small compared with other resources a running Python app will use.除非您计划同时运行数千个实例,否则这样做的资源使用量不应该让您担心 - 与正在运行的 Python 应用程序将使用的其他资源相比,它相当小。

Just for comparison: asyncio coroutines and tasks are the kind of object which one can create thousands of in a process, and it is just "ok", will have a similar overhead.只是为了比较:异步协程和任务是一种可以在一个进程中创建数千个的对象,它只是“ok”,会有类似的开销。

But since you have control of the base class for B there are several ways to do it, without resorting to "monkey patching" - which would be modifying B in place after it was created.但是,由于您可以控制B类,因此有几种方法可以做到这一点,而无需求助于“猴子修补”——这将在 B 创建后就地修改它。 That is often the only alternative when one has to modify a class for which they don't control the code.当必须修改他们不控制代码的类时,这通常是唯一的选择。

Wrapping the method either automatically, when it is retrieved from a B instance, in a lazy way, can spare even this - and sure can be more elegant than wrapping at the base class __init__ :自动包装该方法,当它从B实例中检索时,以一种懒惰的方式,甚至可以省去这一点 - 并且肯定可以比在基类__init__包装更优雅:

If you know beforehand the methods you have to wrap, and is sure they are implemented in subclasses of classes which you control, this can be made by crafting a specialized __getattribute__ method: this way, the method is wrapped only when it is about to get used.如果您事先知道必须包装的方法,并确定它们是在您控制的类的子类中实现的,则可以通过制作专门的__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

As for wrapping foo when B is created, that could give use even less resources - and while it could be done in a metaclass, from Python 3.6 on, the __init_subclass__ special method can handle it, with no need for a custom metaclass.至于在创建 B 时包装foo ,这可以使用更少的资源 - 虽然它可以在元类中完成,但从 Python 3.6 开始, __init_subclass__特殊方法可以处理它,而无需自定义元类。

However, this approach can be tricky if the code might further subclass B in a class C(B): which will again override foo : the wrapper could be called multiple times if the methods use super() calls to foo in the base classes.但是,如果代码可能在class C(B):这将再次覆盖foo :如果方法在基类中使用super()调用foo ,则可以多次调用包装器。 Avoiding the code in the wrapper to run more than once would require some complicated state handling (but it can be done with no surprises).避免包装器中的代码多次运行将需要一些复杂的状态处理(但可以毫无意外地完成)。


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