2

前言

我想知道如何以Python的方式概念化数据类。具体来说,我说的是 DTO(数据传输对象。)

我在@jeff-oneill 问题“<a href="https://stackoverflow.com/questions/3357581/using-python-class-as-a-data-container/">Using Python class as a数据容器”,其中@joe-kington 有一个使用内置namedtuple.

问题

在 python 2.7 文档的第 8.3.4 节中,有一个很好的例子来说明如何组合几个命名元组。我的问题是如何实现反向?

例子

考虑文档中的示例:

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

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

最好是蟒蛇精神。

4

5 回答 5

4

这里是。顺便说一句,如果你经常需要这个操作,你可以创建一个创建函数color_ins,基于pixel_ins. 甚至对于任何 subnamedtuple!

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)

用于提取任意子名称元组的函数(无错误处理):

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)
于 2017-02-21T14:18:27.820 回答
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)

于 2017-02-21T14:17:28.073 回答
1

这是 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,有将多个命名元组组合成一个命名元组的代码,保留字段顺序并跳过此答案中的重复字段。

于 2017-02-22T10:30:43.467 回答
0

您可以这样做的另一种方法是使“像素”的参数与您实际想要的一致,而不是扁平化其组成部分的所有参数。

我认为您应该只使用两个参数,而不是组合Point._fields + Color._fields来获取 Pixel 的字段:locationcolor. 这两个字段可以用你的其他元组初始化,你不必做任何推断。

例如:

# 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'

  

于 2020-10-14T20:43:27.110 回答
0

背景

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

解决方案

您可以将命名元组定义子类化并实现自定义__new__方法来支持它,可选地在途中执行一些数据格式化和验证。有关详细信息,请参阅此参考。

例子

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

Color现在,您可以从任何受支持的值类型(Color, 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
于 2020-11-01T16:33:40.663 回答