代码之家  ›  专栏  ›  技术社区  ›  sds Niraj Rajbhandari

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

  •  4
  • sds Niraj Rajbhandari  · 技术社区  · 1 年前

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

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

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

    我想知道是否可以用类似的方法 data class 实例而不是元组:

    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])
    

    但是 没有 明确的 列表理解。

    当然,结果不会是元组 (xs,ys) 但一本带钥匙的字典 x y 因为,如果没有明确的列表理解,我们将收集 全部的 字段。

    6 回复  |  直到 1 年前
        1
  •  33
  •   Andrej Kesely    1 年前

    您可以定义自定义 __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)
    

    打印:

    (1, 3, 5, 7)
    (2, 4, 6, 8)
    
        2
  •  17
  •   sds Niraj Rajbhandari    1 年前

    具有 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)
    

    或者将其映射为:

    xs, ys = zip(*map(astuple, points))
    
        3
  •  10
  •   Hai Vu    1 年前

    如果您正在寻找使用 zip ,其他人已经回答了。然而,如果您正在寻找最终结果 xs ys 变量。您可以使用列表理解:

    xs = [point.x for point in points]
    ys = [point.y for point in points]
    

    这比其他尝试使用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}")
    

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

    # my_method: 0.42180337477475405
    # astuple_method: 8.835096125025302
    # Ratio astuple:my: 20.9460062517122
    
        4
  •  8
  •   Plagon    1 年前

    如果你尝试用类来做这件事,你会得到一个提示: 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))
    

    现在:

    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))
    
        5
  •  6
  •   Mad Physicist    1 年前

    一个丑陋的解决方案是,提供一个将实例转换为可迭代实例的自定义函数,然后使用 map

    例如:

    def pt2iter(pt):
        yield pt.x
        yield pt.y
    
    xs, ys = zip(*map(pt2iter, points))
    

    可迭代项可以是任何内容。例如

    def pt2iter(pt):
        return pt.x, pt.y
    
        6
  •  3
  •   user2390182    1 年前

    除了使用 astuple ,你可以 XY namedtuple 相反:

    XY = namedtuple('XY', ['x', 'y'])
    

    这甚至是 nametuple 并带来了不变性的优势(这似乎是合乎逻辑的)。当然,迭代性是给定的:

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

    或者,在另一个解决方案的基础上构建,您可以创建一个可迭代的数据类装饰器:

    def idataclass(**kwargs):     
        def deco(cls):
            cls = dataclass(cls, **kwargs)
            cls.__iter__  = lambda s: (getattr(s, field.name) for field  in fields(s))
            return cls
        return deco
    
     
    @idataclass()
    class XY:
        x: float | int
        y: float | int
    
        7
  •  3
  •   sds Niraj Rajbhandari    1 年前

    通过使用 zip var 函数以及 * 运算符来解压缩每个实例的字段。您可以将实例直接传递给 拉链 作为单独的参数,使用 * 操作员:

    from dataclasses import dataclass
    
    @dataclass
    class XY:
        "2d point"
        x: float or int
        y: float or int
    
    points = [XY(1, 2), XY(3, 4), XY(5, 6), XY(7, 8)]
    data_dict = dict(zip(('x', 'y'), zip(*(vars(p).values() for p in points))))
    
    print(data_dict)
    

    结果:

    {'x': (1, 3, 5, 7), 'y': (2, 4, 6, 8)}
    
        8
  •  2
  •   Karim Baidar    1 年前

    您可以使用 吸引器 函数,以提取 x y 的每个实例的属性 XY

    from operator import attrgetter
    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 = map(list, zip(*map(attrgetter('x', 'y'), points)))
    

    然后使用 zip(*…) 语法,并使用转换为列表 地图(列表,…) 作用

    了解更多关于attrgetter的信息: https://note.nkmk.me/en/python-operator-usage/