如何用子类内部状态包装所有 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
,如果方法 toto
、tata
等本身是异步方法并且 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)
我知道
我有一个外部 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
,如果方法 toto
、tata
等本身是异步方法并且 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)