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

2024-09-24 22:30:05 发布

您现在位置: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方法


Tags: 实例方法inselfforfooinitdef
2条回答

一种方法是覆盖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对象的属性查找失败,则调用__getattr__上的Bar。然后,我们尝试查看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

相关问题 更多 >