将多个类方法应用于对象列表的Python方法



我有一个带有一些内置方法的类。这是一个抽象的类外观示例:

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,因为BarFoo都不接受该属性。但是,如果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

最新更新