python 从基类 Package 派生类方法

tzdcorbm  于 2023-02-07  发布在  Python
关注(0)|答案(2)|浏览(140)

假设我有一个基类和一个派生类,如下所示:

class A:
    def foo(self):
        pass

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

我希望 Package 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个闭包的成本。
有没有更好的方法来做这件事?也许是基于元类的方法?

li9yvcax

li9yvcax1#

除非你打算同时运行数千个示例,否则这样做的资源使用量并不重要--与正在运行的Python应用程序所使用的其他资源相比,它是相当小的。
仅供比较:异步协程和任务是一种对象,一个人可以在一个过程中创建数千个,它只是"ok",将有类似的开销。
但是既然你已经控制了B的 * base * 类,有几种方法可以做到这一点,而不用求助于"猴子修补"--那将是在B创建后修改它的位置。当你必须修改一个他们不控制代码的类时,这通常是唯一的选择。
当从B示例检索方法时,以一种懒惰的方式自动 Package 方法,甚至可以避免这种情况--而且肯定比在基类__init__ Package 更优雅:
如果你事先知道你必须 Package 的方法,并且确信它们是在你控制的类的子类中实现的,这可以通过制作一个专门的__getattribute__方法来实现:这样,只有当要使用该方法时,它才被 Package 。

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时 Package foo,这会给use带来更少的资源--虽然它可以在元类中完成,但从Python 3.6开始,__init_subclass__特殊方法可以处理它,而不需要定制元类。
但是,如果代码可能会在class C(B):中进一步子类化B,而class C(B):又将覆盖foo,则这种方法可能会很棘手:如果方法使用super()调用基类中的foo,则 Package 器可能被多次调用。避免 Package 器中的代码运行超过一次将需要一些复杂的状态处理(但可以毫无意外地完成)。

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
5cnsuln7

5cnsuln72#

以下@jsbueno回答
使用
types.MethodType(attr, self)
比用

partial(_capture(attr), self)

因为partial没有描述符,所以返回的属性没有绑定到类示例。只需要将返回 Package 器固定到

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

相关问题