如何从另一个命名元组中推断或子类命名元组?

时间:2017-02-21 13:55:07

标签: python python-2.7 dto namedtuple python-attrs

前言

我想知道如何用 pythonic 方式概念化数据类。 具体来说,我在谈论DTO(Data Transfer Object。)

我在@ jeff-oneill问题“Using Python class as a data container”中找到了一个很好的答案,其中@ joe-kington有使用内置namedtuple的好处。

问题

在python 2.7文档的8.3.4节中,如何组合多个命名元组有很好的example。 我的问题是如何实现相反的目标?

示例

考虑文档中的示例:

>>> p._fields            # view the field names
('x', 'y')

>>> Color = namedtuple('Color', 'red green blue')
>>> Pixel = namedtuple('Pixel', Point._fields + Color._fields)
>>> Pixel(11, 22, 128, 255, 0)
Pixel(x=11, y=22, red=128, green=255, blue=0)

如何从“像素”实例中推断出“颜色”或“点”实例?

最好是 pythonic 精神。

5 个答案:

答案 0 :(得分:3)

在这里。顺便说一句,如果您经常需要此操作,则可以基于color_ins创建pixel_ins创建功能。或者甚至是任何子名称的元组!

from collections import namedtuple

Point = namedtuple('Point', 'x y')
Color = namedtuple('Color', 'red green blue')
Pixel = namedtuple('Pixel', Point._fields + Color._fields)

pixel_ins = Pixel(x=11, y=22, red=128, green=255, blue=0)
color_ins = Color._make(getattr(pixel_ins, field) for field in Color._fields)

print color_ins

输出:Color(red=128, green=255, blue=0)

提取任意subnamedtuple的函数(无错误处理):

def extract_sub_namedtuple(parent_ins, child_cls):
    return child_cls._make(getattr(parent_ins, field) for field in child_cls._fields)

color_ins = extract_sub_namedtuple(pixel_ins, Color)
point_ins = extract_sub_namedtuple(pixel_ins, Point)

答案 1 :(得分:1)

Point._fields + Color._fields只是一个元组。所以这个:

from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
Color = namedtuple('Color', 'red green blue')
Pixel = namedtuple('Pixel', Point._fields + Color._fields)

f = Point._fields + Color._fields

type(f)只是tuple。因此,无法知道它来自何处。

我建议您查看attrs以便轻松执行属性对象。这将允许您进行适当的继承,并避免定义访问字段的所有好方法的开销。

所以你可以做到

import attr

@attr.s
class Point:
    x, y = attr.ib(), attr.ib()

@attr.s
class Color:
    red, green, blue = attr.ib(), attr.ib(), attr.ib()

class Pixel(Point, Color):
    pass

现在,Pixel.__bases__将为您提供(__main__.Point, __main__.Color)

答案 2 :(得分:0)

这是Nikolay Prokopyev extract_sub_namedtuple的替代实现,它使用字典而不是getattr

from collections import namedtuple

Point = namedtuple('Point', 'x y')
Color = namedtuple('Color', 'red green blue')
Pixel = namedtuple('Pixel', Point._fields + Color._fields)

def extract_sub_namedtuple(tup, subtype):
    d = tup._asdict()
    return subtype(**{k:d[k] for k in subtype._fields})

pix = Pixel(11, 22, 128, 255, 0)

point = extract_sub_namedtuple(pix, Point)
color = extract_sub_namedtuple(pix, Color)
print(point, color)

<强>输出

Point(x=11, y=22) Color(red=128, green=255, blue=0)

这个可以写成一行代码:

def extract_sub_namedtuple(tup, subtype):
    return subtype(**{k:tup._asdict()[k] for k in subtype._fields})

但效率较低,因为它必须为tup._asdict()中的每个字段调用subtype._fields

当然,对于这些特定的命名元组,你可以做到

point = Point(*pix[:2])
color = Color(*pix[2:])

但这并不是很优雅,因为它会对父字段位置和长度进行硬编码。

FWIW,将多个命名元组合成一个命名元组的代码,保留字段顺序并跳过this answer中的重复字段。

答案 3 :(得分:0)

另一种方法是使“像素”的参数与您实际想要的对齐,而不是将其组成部分的所有参数展平。

我认为您应该只具有两个参数:Point._fields + Color._fieldslocation,而不是组合color来获取Pixel的字段。可以使用其他元组初始化这两个字段,而无需进行任何推断。

例如:

# Instead of Pixel(x=11, y=22, red=128, green=255, blue=0)
pixel_ins = Pixel(Point(x=11, y=22), Color(red=128, green=255, blue=0))

# Get the named tuples that the pixel is parameterized by
pixel_color = pixel_ins.color
pixel_point = pixel_ins.location

通过将所有参数(例如,主要对象上的x,y,红色,绿色和蓝色)混合在一起,您实际上并不会获得任何收益,但是却失去了很多可读性。如果您的namedtuple参数共享字段,则拼合参数还会引入错误:

from collections import namedtuple 

Point = namedtuple('Point', ['x', 'y'])
Color = namedtuple('Color', 'red green blue')
Hue = namedtuple('Hue', 'red green blue')
Pixel = namedtuple('Pixel', Point._fields + Color._fields + Hue._fields)
# Results in:
#    Traceback (most recent call last):
#      File "<stdin>", line 1, in <module>
#      File "C:\Program Files\Python38\lib\collections\__init__.py", line 370, in namedtuple
#        raise ValueError(f'Encountered duplicate field name: {name!r}')
#    ValueError: Encountered duplicate field name: 'red'

  

答案 4 :(得分:0)

背景

最初,我问这个问题是因为我必须支持一些意大利面条式的代码库,这些代码库经常使用元组,但是没有对其中的值进行任何解释。 进行一些重构之后,我注意到我需要从其他元组中提取一些类型化的信息,并且正在寻找一些无样板且类型安全的方法。

解决方案

您可以继承名为tuple definition的子类,并实现一个自定义__new__方法以支持该方法,可以选择在此途中进行一些数据格式化和验证。有关更多详细信息,请参见此reference

示例

from __future__ import annotations

from collections import namedtuple
from typing import Union, Tuple

Point = namedtuple('Point', 'x y')
Color = namedtuple('Color', 'red green blue')
Pixel = namedtuple('Pixel', Point._fields + Color._fields)

# Redeclare "Color" to provide custom creation method
# that can deduce values from various different types
class Color(Color):

    def __new__(cls, *subject: Union[Pixel, Color, Tuple[float, float, float]]) -> Color:
        # If got only one argument either of type "Pixel" or "Color"
        if len(subject) == 1 and isinstance((it := subject[0]), (Pixel, Color)):
            # Create from invalidated color properties
            return super().__new__(cls, *cls.invalidate(it.red, it.green, it.blue))
        else:  # Else treat it as raw values and by-pass them after invalidation
            return super().__new__(cls, *cls.invalidate(*subject))

    @classmethod
    def invalidate(cls, r, g, b) -> Tuple[float, float, float]:
        # Convert values to float
        r, g, b = (float(it) for it in (r, g, b))
        # Ensure that all values are in valid range
        assert all(0 <= it <= 1.0 for it in (r, g, b)), 'Some RGB values are invalid'
        return r, g, b

现在,您可以从任何受支持的值类型(ColorColor,数字的三元组)中实例化Pixel,而无需使用样板。

color = Color(0, 0.5, 1)
from_color = Color(color)
from_pixel = Color(Pixel(3.4, 5.6, 0, 0.5, 1))

您可以验证所有值是否相等:

>>> (0.0, 0.5, 1.0) == color == from_color == from_pixel
True