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

Posted

技术标签:

【中文标题】类的索引成员作为python中的列表【英文标题】:Index member of class as list in python 【发布时间】:2021-03-08 18:03:58 【问题描述】:

假设我有一个简单的类

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

我现在有兴趣生成一个类,我可以在其中将 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 结构化数组的解决方案,但我只是好奇是否有任何标准解决方案。也许使用元类。谷歌搜索主要是关于如何获取一个类或类似类的所有成员的列表的结果。

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

【问题讨论】:

感觉这里zip 很有用。 list(zip(*zip(t1, t2)) == [tuple(t1), tuple(t2)] 用于独立迭代。 编辑:我正在删除“元类”标签——这主要是对对象的普通操作。 @jsbueno 我不会这么快判断,我觉得尤其是最后一部分只能通过元类来实现。 好的 - 我最终需要一个“类装饰器” - 它几乎是一样的。 - 所以我把它放回去 【参考方案1】:

如果我做对了,您只想一次转换 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__ 方法编写一个类 - 并让 getitem 返回的对象同时具有属性。

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

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 类,在你的问题中存在转换的标量应用 - 可以“转换”它,以便它可以使用序列操作.

比我们更接近最初的metaclass 研究——并且可以通过使用类装饰器来实现。很久以前就引入了类装饰器,以取代元类的某些用途。


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 fcls.__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

上面的“工厂”函数可以变成__new__并插入到装饰类中,然后生成的装饰类将表现得像真正的类——但特别是如果你有内省代码并且需要Foo类是真正的在标量上运行的类,你最好有两个单独的类 - 一个创建序列,另一个处理标量。

在这种情况下,您可以去掉“工厂”函数,让“自动序列”返回 AutoSequence 类本身,然后像这样使用它:


class Foo:
   ...

FooSequence = autosequence(Foo)

【讨论】:

好的,相当直截了当。但是你有一个想法吗?最后一个请求,即能够索引对象或属性? 现在更清楚了,有了 cmets - 我会试一试【参考方案2】:

你可以用一个循环而不是 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))

【讨论】:

支持单行,虽然不是很漂亮/可读。对我的问题的最后一部分有任何想法吗? 那里的“单循环”方法根本不会带来任何收益:Python 代码中的 .append 调用超过了 2 个 for 循环的任何可能开销。 @Xaser “索引到”是什么意思?就做foo.xs[index] @DeepSpace 不记得使用过“into”这个词。我的意思是,就像 numpy 结构化/记录数组一样,foo[0].x 将给我与foo.x[0] 相同的结果。而是foo[0] 生成一个带有标量成员的Foo 实例。 @Xaser 我不记得 numpy 会为 foo[0].xfoo.x[0] 返回相同输出的实例。但是,您可以实现 __getitem__ 以使 foo[...] 将返回您希望返回的任何内容

以上是关于类的索引成员作为python中的列表的主要内容,如果未能解决你的问题,请参考以下文章

Python - 列表 - 第八天

列表List

Python3列表

Python3 列表

python_03

python列表