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

How to wrap all python superclass methods with subclass internal state?

我知道 但我不认为它涵盖了我的情况。

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

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 ].我以前从未使用过 metaclass,所以也许它们是解决方案,但我不能让它们与 dummies 字典一起使用,它是一个实例属性。

我愿意寻求一种使其自动化的解决方案,但也愿意与其他解决方案一起解决“动态”问题。

按照@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

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

解决的问题

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

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

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

如果您的代码是并行的,但使用 asyncio,如果方法 tototata 等本身是异步方法并且 yield对异步循环的控制。如果它们没有被声明为“async”,那么保证没有代码会 运行 并行修改属性值(甚至不会被其他函数或方法 Dummy.toto 调用:如果它不是“async ",它不能将执行交给循环。它甚至可以将新任务安排为 运行,但只有当您 return 执行到主循环时才会触及这些任务,并且从非发生在函数本身结尾的异步函数。

所以,我们回到:只需保存属性值,进行调用,然后恢复它。为多线程情况添加 Lock 条款,就可以了。 假设您无权访问 Dummy 代码,并且 DynamicDummy 的每个实例都与一个实例 Dummy 关联,我们可以在 DynamicDummy 实例上创建一个锁。如果所有 DynamicDummy 都具有 Dummy 的单个实例,则锁必须是 class 属性。

要以透明的方式调用包装方法,您的设计也很好:我只是将其更改为使用 __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)