子类化集合命名元组

31

Python的命名元组(namedtuple)可以作为一个轻量级、不可变的数据类,非常有用。我喜欢将它们用于记录参数而不是使用字典。当需要一些更多的功能时,比如简单的docstring或默认值,你可以很容易地将namedtuple重构为类(class)。然而,我见过一些继承自命名元组的类。它们获得了哪些功能,失去了哪些性能?例如,我会这样实现:

from collections import namedtuple

class Pokemon(namedtuple('Pokemon', 'name type level')):
    """
    Attributes
    ----------
    name : str
        What do you call your Pokemon?
    type : str
        grass, rock, electric, etc.
    level : int
        Experience level [0, 100]
    """
     __slots__ = ()
为了能够清晰地记录属性,__slots__ 被用来防止创建 __dict__(保持命名元组轻量级的特性)。
有没有更好的轻量级数据类推荐来记录参数?请注意,我正在使用 Python 2.7。

1
另外,还可以参考这篇关于替代数据容器的文章 https://dev59.com/V3A75IYBdhLWcg3wUHWQ#47784683,例如 NamedTuple 和 dataclasses。 - pylang
1个回答

33

新更新:

在 Python 3.6+ 中,您可以使用新的类型语法并创建 typing.NamedTuple。新语法支持所有常见的 Python 类创建功能(文档字符串、多重继承、默认参数、方法等),从3.6.1版本开始可用。

import typing

class Pokemon(MyMixin, typing.NamedTuple):
    """
    Attributes
    ----------
    name : str
        What do you call your Pokemon?
    type : str
        grass, rock, electric, etc.
    level : int
        Experience level [0, 100]
    """
    name: str
    type: str
    level: int = 0 # 3.6.1 required for default args

    def method(self):
        # method work

这个版本创建的类对象与原始的collections.namedtuple大多是等效的,除了一些细节。您也可以使用与旧命名元组相同的语法:
Pokemon = typing.NamedTuple('Pokemon', [('name', str), ('type', str), ('level', int)])

原始答案


简短回答:不行,除非你使用的是Python < 3.5

P3文档似乎很清楚地暗示了,除非您需要添加计算字段(即描述符),否则子类化namedtuple不被认为是规范的方法。这是因为您可以直接更新docstrings(它们现在从3.5开始可写!)。

子类化对于添加新的存储字段没有用。相反,只需从_fields属性创建一个新的命名元组类型...

Docstrings可以通过直接分配给__doc__字段来自定义...

更新:

现在,在最新版本的Python中,还有另外几种轻量级数据类的可能性。

其中之一是types.SimpleNamespace(Python 3.3及更高版本)。它的结构不像namedtuple那样结构化,但并不总是必要的。

需要注意的一件事是:默认情况下,要在实例化类时明确指定字段名称。不过,可以通过调用super().__init__来轻松解决这个问题:

from types import SimpleNamespace

class Pokemon(SimpleNamespace):
    """
    Attributes
    ----------
    name : str
        What do you call your Pokemon?
    type : str
        grass, rock, electric, etc.
    level : int
        Experience level [0, 100]
    """
    __slots__ = ("name", "type", "level")
    # note that use of __init__ is optional
    def __init__(self, name, type, level):
        super().__init__(name=name, type=type, level=level)

另一个有趣的选择-Python 3.7已经提供了此功能-是dataclasses.dataclass(还请参阅PEP 557):

from dataclasses import dataclass

@dataclass
class Pokemon:
    __slots__ = ("name", "type", "level")
    name: str  # What do you call your Pokemon?
    type: str  # grass, rock, electric, etc.
    level: int = 0  # Experience level [0, 100]

请注意,这两个建议默认情况下都是可变的,并且不需要__slots__

7
当在通过继承 NamedTuple 的方式创建子类时,其他类都会被忽略。所以在第一个例子中,Pokemon 无法使用在 MyMixin 中声明的方法。你可以在这里查看我的问题以获取更多信息:https://dev59.com/sbnoa4cB1Zd3GeqPX9PH - Wayne Chang
MyMixin 定义在哪里? - Stevoisiak
@Stevoisiak 这只是一个可选的占位符。我包含它是为了演示在混合其他父类行为时通常使用的继承顺序。 - Rick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接