从基类包装派生的类方法

发布于 2025-02-08 09:28:19 字数 882 浏览 0 评论 0原文

想象一下,我有一个基础和一个类似的类别:

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<界方法代码>。看来我必须为我创建的每个实例支付2个结局的费用。

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

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. Modifying any part of B is not allowed (I don’t own 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>>. It would seem that I would have to pay the cost of 2 closures for each instance I create.

Is there a better way to do this? Perhaps a metaclass based approach?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

满栀 2025-02-15 09:28:20

除非您打算同时拥有数千个实例,否则这样做的资源使用情况不应关注您 - 与运行Python应用程序将使用的其他资源相比,它相当小。

只是为了进行比较:Asyncio Coroutines和Task是一种可以在一个过程中创建数千个的对象,并且它只是“确定”,它将具有类似的开销。

但是,由于您可以控制 base 类的b有几种方法可以执行此操作,而无需求助于“ monkey patching” - 它将在其之后修改B被创建了。当一个人必须修改不控制代码的类时,这通常是唯一的选择。

将该方法自动包装,当它从b实例中检索时,以懒惰的方式可以节省这一点 - 并且肯定可以比在基类__ __ INT __ INT __ :

如果您事先知道您必须包装的方法,并且确定它们是在您控制的类的子类中实现的,则可以通过制作专业__ 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

至于包装foo创建b时,可以提供更少的资源 - 虽然可以在元素级别上完成,从python 3.6 on,__ INIT_SUBCLASS __特殊方法可以处理它,而无需自定义元素。

但是,如果代码可能在A c(b)中进一步子类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

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.

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.

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. 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__:

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.

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.

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. 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
辞慾 2025-02-15 09:28:20

以下内容@jsbueno wands bess

使用

type.methodtype(attr,self)

要好于使用

partial(_capture(attr), self)

,因为部分没有描述符,因此返回的属性不绑定到类实例。只需要修复返回包装器

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

Following @jsbueno answer

Using

types.MethodType(attr, self)

is better than using

partial(_capture(attr), self)

since partial doesn't have a descriptor so the returned attribute is not bound to the class instance. just need to fix the return wrapper to

def wrapper(self, *args, **kwargs):
    print("wrapped")
    return f(*args, **kwargs) 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文