0

假设我有一个简单的类

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

我现在对生成一个类感兴趣,我可以在其中将一个可迭代的 for 传递给初始化程序并返回一个我可以访问成员bar的实例,并且喜欢大小为 的迭代,即Fooxybar

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 伟大的。

4

2 回答 2

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)

map您可以使用and隐藏循环zip

class Foo:
    def __init__(self, bar):
        self.xs, self.ys = zip(*map(lambda e: (e.x, e.y), bar))
于 2020-11-25T20:31:19.097 回答
2

如果我做对了,您只想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__允许 PythonFoo在 for 循环迭代中自动使用的实例相结合)

现在,如果你想让它变得非常有趣,让我们假设你的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 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

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

在这种情况下,您可以剥离“工厂”功能,让“自动序列”返回 AutoSequence 类本身,并像这样使用它:


class Foo:
   ...

FooSequence = autosequence(Foo)
于 2020-11-25T21:31:26.157 回答