class 的索引成员作为 python 中的列表

Index member of class as list in python

假设我有一个简单的class像

class Foo:
  def __init__(bar):
    self.x = transform1(bar)
    self.y = transform2(bar)

我现在对生成一个 class 感兴趣,我可以在其中将 bar 的可迭代对象传递给初始化程序并取回 Foo 的实例,我可以在其中访问成员 xy 喜欢大小为 bar 的迭代器,即

x = [1, 2, 3]
foo = Foo(x)
plt.plot(foo.x, foo.y)

我知道很容易做到

foo = [Foo(elem) for elem in x]
plt.plot([elem.x for elem in foo], [elem.y for elem in foo])

但这感觉很冗长而且可能不是很有效。我可以粗略地想象一个带有 numpy 结构化数组的解决方案,但我只是好奇是否有任何标准解决方案。也许使用 metaclasses。谷歌搜索主要是关于如何获取 class 或类似的所有成员列表的结果。

如果有人甚至可以提出一种解决方案,允许索引 对象 foo 其成员,这将是伟大的。

您可以用一个循环而不是 2 个循环来完成:

class Foo:
    def __init__(self, bar):
        self.xs = []
        self.ys = []
        for elem in bar:
            self.xs.append(elem.x) 
            self.ys.append(elem.y)

您可以使用 mapzip 隐藏循环:

class Foo:
    def __init__(self, bar):
        self.xs, self.ys = zip(*map(lambda e: (e.x, e.y), bar))

如果我没看错,您只想一次转换 bar 中的所有元素。 只做它,而不是一次一个标量。就去做吧:

class Foo:
  def __init__(bar):
    self.x = [transform1(el) for el in bar] 
    self.y = [transform2(el) for el in bar]

真的就这么简单。如果您想使用线程或进程并行 运行 transform1 和 transform2,或者如果您希望以惰性方式根据需要计算所有变换,则有一些奇特的东西。

但是要绘制图形,列表就可以了。在单个 for 循环而不是两个列表推导中完成它甚至没有任何好处——使用 for 本身的迭代所花费的时间可以忽略不计。


如果您希望能够索引实例本身,并取回具有所需属性的对象,则必须使用 __getitem__ 方法编写 class - 并具有由 getitem return编辑的对象具有两个属性。

为此你可以使用一个更简单的 class,代表你的标量,并且根据你的需要,这个更简单的 class 可以是一个命名元组:

from collections import namdtuple

ScalarFoo = namedtuple("ScalarFoo", "x y")

class Foo:
  def __init__(bar):
    self.x = [transform1(el) for el in bar] 
    self.y = [transform2(el) for el in bar]
  def __getitem__(self, index):
       return ScalarFoo(self.x[index], self.y[index])
  def __len__(self):
       return len(self.x)

__len__ 方法结合 __getitem__ 允许 Python 在 for 循环迭代中自动使用 Foo 的实例)

现在,如果你想让它变得非常有趣,让我们假设你的 Foo class,在你的问题中存在变换的标量应用 - 可以“变换”以便它可以使用序列进行操作。

比我们更接近最初的 metaclass 研究 - 并且可以通过使用 class 装饰器来实现。 Class 装饰器在很久以前就被引入以取代 metaclasses 的一些用途。


def autosequence(cls):
    """Transforms the received class into a factory,
    so that if a sequence or iterator is passed as the first
    argument to it, a new, sequence class is used. If the
    resulting class is used in an iteration or as a sequence,
    an instance of the original class is returned
    """
    
    class AutoSequence:
        def __init__(self, *args, **kw):
            self.sequence = list(args[0])
            self.other_args = args[1:]
            self.kw = kw
        
        def __getitem__(self, index):
            return cls(self.sequence[index], *self.other_args, **self.kw)
        
        def __len__(self):
            return len(self.sequence)
        
        def __repr__(self):
            return f"Lazy sequence of f{cls.__name__} objects with {len(self)} elements"
        
        
    def factory(*args, **kw):
        if args and hasattr(args[0], "__len__") or hasattr(args[0], "__iter__"):
            return AutoSequence(*args, **kw)
        return cls(*args, **kw)
        
    factory.__name__ = cls.__name__
    return factory



def transform1(a):
    return a

def transform2(a):
    return a ** 2


@autosequence
class Foo:
    def __init__(self, bar):
        self.x = transform1(bar)
        self.y = transform2(bar)
        
    def __repr__(self):
        return f"{self.__class__.__name__}({self.x}, {self.y})"
    

下面是它在交互式解释器中的表现:

In [24]: a = Foo([1,2,3])                                                                            

In [25]: a[2]                                                                                        
Out[25]: Foo(3, 9)

In [26]: Foo(4)                                                                                      
Out[26]: Foo(4, 16)

In [27]: Foo(4).y                                                                                    
Out[27]: 16

In [28]: a[2].y                                                                                      
Out[28]: 9

上面的“factory”函数可以做成一个 __new__ 并插入到修饰的 class 中,然后得到的修饰的 class 将表现得像真正的 [=50] =] - 但特别是如果你有内省代码并且需要 Foo class 是真实的 class 在标量上运行,你最好有两个单独的 classes - 一个创建序列,另一个处理标量。

在那种情况下,您可以剥离“工厂”功能,将“自动序列”设置为 return 自动序列 class 本身,然后像这样使用它:


class Foo:
   ...

FooSequence = autosequence(Foo)