我有一个带有一些内置方法的类。这是一个抽象的类外观示例:
class Foo:
def __init__(self):
self.a = 0
self.b = 0
def addOneToA(self):
self.a += 1
def addOneToB(self):
self.b += 1
为了简单起见,我将内置方法总共减少到了2个,但实际上我的类有接近20个。
接下来,我有另一个类,它被设计用于处理Foo
实例的列表。
class Bar:
def __init__(self, fooInstances):
self.fooInstances = fooInstances
# Bar([Foo(), Foo(), Foo()])
如果我想将其中一个Foo
方法应用于Bar
中的Foo
实例,该怎么办?
class Bar:
# ...
def addOneToA(self):
for fooInstance in self.fooInstances:
fooInstance.addOneToA()
def addOneToB(self):
for fooInstance in self.fooInstances:
fooInstance.addOneToB()
上面的例子是我所描述的一种方法,但如果有20个Foo
类方法,那么这样做似乎需要大量重复代码。或者,我可以做这样的事情:
class Bar:
# ...
def applyFooMethod(self, func, *args):
for fooInstance in self.fooInstances:
fooInstance.func(args)
但我更希望有一些东西允许我在Bar
上调用.addOneToA()
,并将其应用于Bar
中的所有Foo
实例。有没有一种干净的方法可以做到这一点,而不在Bar
中定义Foo
的所有方法?
一种方法是覆盖Bar
:的__getattr__
class Bar:
def __init__(self, fooInstances):
self.fooInstances = fooInstances
def __getattr__(self, attr):
try:
getattr(self.fooInstances[0], attr)
except AttributeError:
raise AttributeError(f"'Bar' object has no attribute '{attr}'")
else:
def foo_wrapper(*args, **kwargs):
for foo_inst in self.fooInstances:
getattr(foo_inst, attr)(*args, **kwargs)
return foo_wrapper
如果Bar
对象上的属性查找失败,则调用Bar
上的__getattr__
。然后,我们尝试查看Foo
实例是否具有该属性;如果不是,则引发AttributeError
,因为Bar
和Foo
都不接受该属性。但是,如果Foo
确实有它,我们会返回一个函数,当调用该函数时,它会在Bar
对象中的Foo
的每个瞬间调用方法(attr
(。
用法:
...
# changed this method in Foo to see the passing-an-argument case
def addOneToA(self, val):
self.a += 1
print(f"val = {val}")
...
>>> bar = Bar([Foo(), Foo(), Foo()])
>>> bar.addOneToB()
>>> [foo.b for foo in bar.fooInstances]
[1, 1, 1]
>>> bar.addOneToA(val=87) # could also pass this positionally
val = 87
val = 87
val = 87
>>> bar.this_and_that
AttributeError: 'Bar' object has no attribute 'this_and_that'
另一种方法是使用setattr()
创建一个函数,该函数在构造bar
对象时调用applyFooMethod()
。这样,dir(bar)
将显示Foo
的方法。
class Bar:
def __init__(self, fooInstances):
self.fooInstances = fooInstances
foo0 = fooInstances[0]
for method_name in dir(foo0):
method = getattr(foo0, method_name)
# Make sure it's callable, but not a dunder method
if callable(method) and not method_name.startswith("__"):
# Make a lambda function with a bound argument for method_name
# We simply need to call applyFooMethod with the correct name
mfunc = lambda m=method_name, *args: self.applyFooMethod(m, *args)
# Set the attribute of the `bar` object
setattr(self, method_name, mfunc)
def applyFooMethod(self, func_name, *args):
for fooInstance in self.fooInstances:
func = getattr(fooInstance, func_name)
func(*args)
然后,你可以这样运行它:
foos = [Foo(), Foo(), Foo(), Foo()]
bar = Bar(foos)
dir(bar)
# Output:
# [...the usual dunder methods...,
# 'addOneToA',
# 'addOneToB',
# 'applyFooMethod',
# 'fooInstances']
现在,我们可以调用bar.addOneToA()
:
bar.addOneToA()
for f in foos:
print(f.a, f.b)
bar.addOneToB()
for f in foos:
print(f.a, f.b)
其首先递增所有a
值,然后递增所有b
值。
1 0
1 0
1 0
1 0
1 1
1 1
1 1
1 1