前言
我想知道如何用 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 精神。
答案 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._fields
和location
,而不是组合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
现在,您可以从任何受支持的值类型(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