Python zip 魔法用于类而不是元组

sds*_*sds 25 python

Pythonzip函数(在某种程度上)是它自己的逆函数,因此我们可以这样做:

points = [(1,2), (3,4), (5,6), (7,8)]
xs, ys = zip(*points)
Run Code Online (Sandbox Code Playgroud)

现在xs=[1,3,5,7]ys=[2,4,6,8]

我想知道是否可以使用数据类实例而不是元组来完成类似的操作:

from dataclasses import dataclass

@dataclass
class XY:
    "2d point"
    x: float | int
    y: float | int

points = [XY(1,2), XY(3,4), XY(5,6), XY(7,8)]
xs, ys = zip(*[(p.x,p.y) for p in points])
Run Code Online (Sandbox Code Playgroud)

没有明确列表理解。

当然,结果不会是一个元组,(xs,ys)而是一个带有键的字典xy因为如果没有显式的列表理解,我们将收集 所有字段。

And*_*ely 37

您可以__iter__在数据类中定义自定义魔术函数:

from dataclasses import dataclass

@dataclass
class XY:
    "2d point"
    x: float | int
    y: float | int

    def __iter__(self):
        yield self.x
        yield self.y

points = [XY(1,2), XY(3,4), XY(5,6), XY(7,8)]

xs, ys = zip(*points)
print(xs)
print(ys)
Run Code Online (Sandbox Code Playgroud)

印刷:

(1, 3, 5, 7)
(2, 4, 6, 8)
Run Code Online (Sandbox Code Playgroud)

  • 这个 `__iter__` 版本的速度大约是使用 `astuple` 版本的 10 倍。做得好。 (4认同)

Kel*_*ndy 21

astuple

from dataclasses import dataclass, astuple

@dataclass
class XY:
    "2d point"
    x: float | int
    y: float | int
    def __iter__(self):
        return iter(astuple(self))

points = [XY(1,2), XY(3,4), XY(5,6), XY(7,8)]
xs, ys = zip(*points)
Run Code Online (Sandbox Code Playgroud)

或者改为映射它:

xs, ys = zip(*map(astuple, points))
Run Code Online (Sandbox Code Playgroud)


Hai*_* Vu 10

如果您正在寻找使用的解决方案zip,其他人已经回答了。但是,如果您正在寻找最终结果,那就是xsys变量。您可以只使用列表理解:

xs = [point.x for point in points]
ys = [point.y for point in points]
Run Code Online (Sandbox Code Playgroud)

这比尝试使用 zip 的其他解决方案更简单、更容易理解。

更新

回应 sds 的评论:是的,需要两行代码,但是

  1. 代码易于阅读
  2. 比较简单
  3. 速度快得多

为了演示速度,我将我的解决方案与astuple实际解决方案进行了比较:

from dataclasses import dataclass, astuple
import timeit


@dataclass
class XY:
    """2d point"""

    x: float | int
    y: float | int

    def __iter__(self):
        return iter(astuple(self))


def my_method(points):
    xs = [point.x for point in points]
    ys = [point.y for point in points]
    return xs, ys


def astuple_method(points):
    xs, ys = zip(*points)
    return xs, ys


points = [XY(1, 2), XY(3, 4), XY(5, 6), XY(7, 8)]

# My time
my_time = timeit.timeit(
    stmt="my_method(points)",
    globals=globals(),
)
print(f"\n# my_method: {my_time}")

# astuple time
astuple_time = timeit.timeit(
    stmt="astuple_method(points)",
    globals=globals(),
)

print(f"# astuple_method: {astuple_time}")
print(f"# Ratio astuple:my: {astuple_time / my_time}")
Run Code Online (Sandbox Code Playgroud)

下面是一个示例输出,它表明我的解决方案快了大约 20 倍:

# my_method: 0.42180337477475405
# astuple_method: 8.835096125025302
# Ratio astuple:my: 20.9460062517122
Run Code Online (Sandbox Code Playgroud)


Pla*_*gon 8

如果您尝试使用类来执行此操作,您会得到提示:TypeError: 'XY' object is not iterable

使类可迭代(add __iter__):

from dataclasses import dataclass, fields


@dataclass()
class XY:
    "2d point"
    x: float | int
    y: float | int

    def __iter__(self):
        return (getattr(self, field.name) for field in fields(self))
Run Code Online (Sandbox Code Playgroud)

现在:

points = [XY(1, 2), XY(3, 4), XY(5, 6), XY(7, 8)]
xs, ys = zip(*points)

xs,ys # ((1, 3, 5, 7), (2, 4, 6, 8))
Run Code Online (Sandbox Code Playgroud)


Mad*_*ist 6

一种丑陋的解决方案,但不需要您修改现有的类,是提供一个自定义函数,将实例转换为可迭代对象,然后将其应用到map.

例如:

def pt2iter(pt):
    yield pt.x
    yield pt.y

xs, ys = zip(*map(pt2iter, points))
Run Code Online (Sandbox Code Playgroud)

可迭代对象可以是任何东西。例如

def pt2iter(pt):
    return pt.x, pt.y
Run Code Online (Sandbox Code Playgroud)