如何用子类内部状态包装所有 python 超类方法?

Posted

技术标签:

【中文标题】如何用子类内部状态包装所有 python 超类方法?【英文标题】:How to wrap all python superclass methods with subclass internal state? 【发布时间】:2021-09-25 01:27:59 【问题描述】:

我知道this question,但我认为它不包括我的情况。

我有一个带有许多方法的外部 Dummy 类,所有方法都使用实例属性。我希望能够将其作为参数传递,而不是使用此实例属性。我的解决方案是保留一组假人,并在必要时使用具有适当属性的人。

class Dummy:

    def __init__(self, prefix="dum"):
        self.prefix = prefix

    def toto(self):
        return f"self.prefix_toto"

    def titi(self):
        return f"self.prefix_titi"

    def tata(self):
        return f"self.prefix_tata"


class DynamicDummy:

    def __init__(self):
        self.dummies = 

    def _get_dummy(self, prefix):
        dummy = self.dummies.get(prefix)
        if dummy is None:
            dummy = Dummy(prefix)
            self.dummies[prefix] = dummy
        return dummy

    def toto(self, prefix):
        dummy = self._get_dummy(prefix)
        return dummy.toto()

    def titi(self, prefix):
        dummy = self._get_dummy(prefix)
        return dummy.titi()

    def tata(self, prefix):
        dummy = self._get_dummy(prefix)
        return dummy.tata()

问题是,有超过 3 种方法,我希望它是自动的,这样我就不必在每次 Dummy 发生变化时更改我的 DynamicDummy。我以前从未使用过元类,所以也许它们是解决方案,但我不能让它们与 dummies 字典一起使用,这是一个实例属性。

我愿意寻求一种使其自动化的解决方案,但也愿意为“动态性”问题提供另一种解决方案。

【问题讨论】:

为什么没有像get_name(self, name, prefix) 这样的单一方法,而是像get_name('toto', 'some_prefix') 那样调用它,而不是使用多个相同的方法?这适用于两个课程。 你也可以看看customization of attribute access 因为这是一个简化的例子,实际的方法是不一样的 即使在这种情况下,我仍然建议查看属性访问的自定义。 【参考方案1】:

按照@buran 的建议,我修改了__getattribute__ 方法。

class SmartDynamicDummy(Dummy):

    def __init__(self):
        self.dummies = 

    def _get_dummy(self, prefix):
        dummy = self.dummies.get(prefix)
        if dummy is None:
            dummy = Dummy(prefix)
            self.dummies[prefix] = dummy
        return dummy

    def _wrapper(self, func, func_name):
        @wraps(func)
        def wrapped(*args, **kwargs):
            args = list(args)
            prefix = args.pop(0)
            args = tuple(args)
            dummy = self._get_dummy(prefix)
            dummy_func = getattr(dummy, func_name)
            return dummy_func(*args, **kwargs)

        return wrapped


    def __getattribute__(self, name):
        attr = super(SmartDynamicDummy, self).__getattribute__(name)
        if isinstance(attr, types.MethodType) and not name.startswith('_'):
            # attr.__name__ and name can be different if attr is a decorated function
            attr = self._wrapper(attr, name)
        return attr

【讨论】:

策略很好 - 但要让行为保持你提到的你不想要的行为有点复杂:这种设计仍然需要“dummy_collection”属性。【参考方案2】:

如果您在拨打电话之前更改了目标实例上的 prefix 属性,它将保持更改并正常工作 - 不需要为 prefix 设置具有不同值的实例集合。一种情况是值可能会在中途更改,如果您的应用程序使用线程并行化 - 在这种情况下,可以在 DynamicDummy 上调用其他方法之一,在另一个调用结束之前需要不同的“前缀”。这就是Locks可以解决的问题。

元类在这里并没有真正的作用。当然,可以设计一个涉及元类的复杂事物,但我们只是处理普通的属性设置;

所以,换句话说:如果你的程序没有并行运行,一旦你输入SmartDummy.toto,并且这会调用一个 Dummy 实例.toto(),就不可能调用同一个 Dummy 实例的另一个方法,直到两个调用都已解决 - 因此您只需在 SmartDummy.toto 中设置所需的值,然后在关联的虚拟实例中调用该方法。

如果您的代码确实并行运行,但使用multiprocessing 模型:同样适用:在每个进程中,SmartDummy 的实例就像在串行程序中一样运行,并且没有任何外部事物可以更改 @987654330 SmartDummy.toto 之前的 @ 属性解析。

如果您的代码是并行的,但使用asyncio,如果tototata 等方法本身是异步方法并且将控制权交给异步循环,则可以进行中途更改。如果它们未声明为“异步”,则保证不会并行运行可能修改属性值的代码(甚至不能通过其他函数或方法 Dummy.toto 调用:如果它不是“异步”,则不能让循环执行。它甚至可以安排新任务的运行,但只有当您将执行返回到主循环时才会触及这些任务,并且来自发生在函数本身末尾的非异步函数。

所以,我们回到:只需保存属性值、拨打电话并恢复它。为多线程情况添加 Lock 规定,您就可以了。 假设您无权访问Dummy 代码,并且DynamicDummy 的每个实例都关联一个Dummy 实例,我们可以在DynamicDummy 实例上创建一个锁。如果所有DynamicDummys 都拥有Dummy 的一个实例,那么锁就必须是一个类属性。

要以透明的方式调用包装的方法,您得到的设计也很好:我只是将其更改为使用__getattr__,因为它更简单:

import threading

class DymamicDummy:
    def __init__(self, ...):
        ...
        self.dummy = Dummy()
        self.lock = threading.Lock()
        
    def _dispatcher(self, method):
        # the same thign you found your design: 
        # 'prefix' is always the first positional argument. Change it to a named parameter if desired.
        def wrapper(prefix, *args, **kwargs):
            with self.lock:
                original_prefix = self.dummy.prefix
                self.dummy.prefix = prefix
                try:
                    result = method(*args, **kwargs)
                finally:
                    self.dummy.prefix =self.dummy.prefix
            return result
            
        return wrapper
        
    def __getattr__(self, method_name):
        # using __getattr__ instead of __getattribute__:
        # this only gets called for methods or attributes that do not exist
        # on the current instance anyway: so no need to further testing,
        # just extract the value from Dummy.
        method = getattr(self.dummy, method_name)
        if not callable(method):
            return method
        return _dispatcher(method)
    

【讨论】:

感谢您的回答。我真正的课不是那么简单。我的“前缀”实际上是一个时区,每个 Dummy 实例都有一个数据帧,该数据帧根据时区进行不同的解释。仅仅改变时区是不够的。

以上是关于如何用子类内部状态包装所有 python 超类方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何用 Enumerator 的特定子类包装数组?

李瑞红201771010111

Java中类的继承

Python子类方法从超类方法继承装饰器

Python学习--类和对象

在 Python 3 中获取所有超类