为什么Python不支持记录类型?(即可变的具名元组)

58
为什么Python没有原生支持记录类型?这与具有可变版本的namedtuple有关。
我可以使用namedtuple._replace. 但是,我需要将这些记录放入集合中,由于namedtuple._replace创建另一个实例,因此我还需要修改集合,这很快变得混乱。
背景: 我有一个设备,需要通过TCP/IP轮询来获取其属性。即它的表示形式是一个可变对象。
编辑: 我有一组需要轮询的设备。
编辑: 我需要遍历对象并使用PyQt显示其属性。我知道我可以添加像__getitem__和__iter__这样的特殊方法,但我想知道是否有更简单的方法。
编辑: 我希望有一种类型,其属性是固定的(就像我的设备一样),但是可变的。

2
创建一个类,或使用字典。两者都是可变的,都允许您通过名称访问其中的值。 - dappawit
5
@dappawit,没错。但是使用dict时,我不能像使用字段一样方便地使用属性。我一直在避免使用类,因为我需要将对象视为属性的集合进行迭代。我将编辑我的帖子以指定这个要求。我知道我可以随时添加特殊方法来使其像集合一样处理。但我想知道是否有更简单的方法。 - Salil
一个类实例有一个__dict__属性,它是一个字典。你可以遍历它。请查看Cameron的答案和Chris Lutz的评论。 - dappawit
谢谢dappawit。我想使用现有的类型,而不是使用特殊方法模拟集合类型。但是,是的,我必须这样做。 - Salil
11个回答

56

Python <3.3

你的意思是这样吗?

class Record(object):
    __slots__= "attribute1", "attribute2", "attribute3",

    def items(self):
        "dict style items"
        return [
            (field_name, getattr(self, field_name))
            for field_name in self.__slots__]

    def __iter__(self):
        "iterate over fields tuple/list style"
        for field_name in self.__slots__:
            yield getattr(self, field_name)

    def __getitem__(self, index):
        "tuple/list style getitem"
        return getattr(self, self.__slots__[index])

>>> r= Record()
>>> r.attribute1= "hello"
>>> r.attribute2= "there"
>>> r.attribute3= 3.14

>>> print r.items()
[('attribute1', 'hello'), ('attribute2', 'there'), ('attribute3', 3.1400000000000001)]
>>> print tuple(r)
('hello', 'there', 3.1400000000000001)

请注意,提供的方法仅仅是可能方法的样例。

Python ≥3.3 更新

你可以使用types.SimpleNamespace

>>> import types
>>> r= types.SimpleNamespace()
>>> r.attribute1= "hello"
>>> r.attribute2= "there"
>>> r.attribute3= 3.14

dir(r) 会提供给你属性名称(当然会过滤掉所有以 .startswith("__") 开头的内容)。


1
太好了。感谢你的回答。只是我希望有一个内置/标准库数据结构。 - Salil
SimpleNamespace 可以满足需求。谢谢。 - Salil
WTF!Python 继续搞砸他们的模块逻辑... 为什么这不在 collections 中呢? - u0b34a0f6ae
4
看起来将SimpleNamespace放在“types”模块中是有道理的。如果你看一下“types”模块中的许多“类”,你会发现它们都是通过使用“type”函数提取支持特定值的类型(例如LambdaType = type(lambda: None))而获得的。猜猜SimpleNamespace是如何实现的?SimpleNamespace = type(sys.implementation) - new123456
4
因为它不是集合,就像 class X(): pass 这样的空类一样,它也不是集合。最重要的是,它没有迭代或大小的概念。你为什么认为它应该在 collections 中? - l4mpi
1
请注意,list(r.__dict__)将返回用户定义的属性名称,无需进行“__”过滤。 - EquipDev

18

你为什么不能使用普通的字典呢?在你特定的情况下,属性似乎没有特定的顺序。

或者,你也可以使用类实例(具有良好的属性访问语法)。如果你希望避免为每个实例创建一个__dict__,可以使用__slots__

我还找到了一种名为“记录”的"recipe for 'records'",它们被描述为可变命名元组。它们是使用类实现的。

更新:

既然你说顺序对你的场景很重要(而且你想遍历所有属性),那么OrderedDict似乎是正确的选择。这是Python 2.7的标准collections模块的一部分;对于Python < 2.7,互联网上有其他implementations

要添加属性样式访问,可以这样子类化:

from collections import OrderedDict

class MutableNamedTuple(OrderedDict):
    def __init__(self, *args, **kwargs):
        super(MutableNamedTuple, self).__init__(*args, **kwargs)
        self._initialized = True

    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        if hasattr(self, '_initialized'):
            super(MutableNamedTuple, self).__setitem__(name, value)
        else:
            super(MutableNamedTuple, self).__setattr__(name, value)

然后你可以这样做:
>>> t = MutableNamedTuple()
>>> t.foo = u'Crazy camels!'
>>> t.bar = u'Yay, attribute access'
>>> t.foo
u'Crazy camels!'
>>> t.values()
[u'Crazy camels!', u'Yay, attribute access']

@Chris:我最喜欢那种方法,我认为(你的记忆是正确的,那些是正确的方法) - Cameron
1
+1 是为了指定 slots 只为每个类创建一次,而 dict 则为每个实例创建。我已编辑我的问题以包括需要排序的需求。此外,我知道这个记录配方;然而,我想知道 Python 社区为什么认为没有必要使用标准记录类型。 - Salil
3
如果你需要排序,可以尝试使用OrderedDict。我相信它也在collections模块中。 - dappawit
1
谢谢dappawit。我更喜欢属性固定但可变的类型(就像在我的设备中一样)。我想我可能需要按照Cameron指定的记录类型去做。 - Salil
@ChrisLutz @Cameron 使用getattr听起来很有趣,但我建议不要这样做,因为字段名称会与字典的公共方法(如getitems)发生冲突。这可能会导致有趣的错误。 - Kos
显示剩余3条评论

11

可以使用一个空类和它的实例来实现,像这样:

>>> class a(): pass
... 
>>> ainstance = a()
>>> ainstance.b = 'We want Moshiach Now'
>>> ainstance.b
'We want Moshiach Now'
>>> 

此外,有关更多信息,请参见此答案以及此stackoverflow问题 - Abbafei
谢谢Abafei。我更喜欢一个属性固定但可变的类型(就像在我的设备中一样),我已经相应地更新了问题。 - Salil

10

有一个类似于namedtuple但是可变的库,叫做recordtype。

包的主页:http://pypi.python.org/pypi/recordtype

简单示例:

from recordtype import recordtype

Person = recordtype('Person', 'first_name last_name phone_number')
person1 = Person('Trent', 'Steele', '637-3049')
person1.last_name = 'Terrence';

print person1
# Person(first_name=Trent, last_name=Terrence, phone_number=637-3049)

简单的默认值示例:

Basis = recordtype('Basis', [('x', 1), ('y', 0)])

按顺序遍历person1的字段:

map(person1.__getattribute__, Person._fields)

10
namedlist 是同一作者更新的 Python 包,支持 Python 3,并自2014年起积极开发。 - simonzack

8
这个问题很旧,但是为了完整起见,Python 3.7有dataclasses,它们几乎就是记录。
>>> from dataclasses import dataclass
>>>
>>> @dataclass
... class MyRecord:
...     name: str
...     age: int = -1
...
>>> rec = MyRecord('me')
>>> rec.age = 127
>>> print(rec)
MyRecord(name='me', age=127)

attrs第三方库为Python 2和Python 3提供了更多功能。如果需求更多地围绕无法在本地保留的内容,而不是仅使用标准库,那么将依赖项作为供应商也没有问题。dephell有一个很好的助手来完成这个任务。


4
这个答案重复了另一个。有一种可变的替代方法可以使用collections.namedtuple,它叫做recordclass。它具有相同的API和最小的内存占用(实际上也更快)。它支持赋值操作。例如:
from recordclass import recordclass

Point = recordclass('Point', 'x y')

>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> print(p.x, p.y)
1 2
>>> p.x += 2; p.y += 3; print(p)
Point(x=3, y=5)

这里有更完整的示例(它还包括性能比较)。


2
在与之密切相关的问题“Python中可变命名元组的存在?”中,使用了13个测试来比较6种可变替代方案和namedtuple。
截至2016年1月11日,最新的namedlist 1.7已通过Python 2.7和Python 3.5的所有这些测试。它是一个纯Python实现。
根据这些测试,第二好的候选者是recordclass,它是一个C扩展。当然,是否优先选择C扩展取决于您的要求。
有关更多详细信息,特别是有关测试的信息,请参见“Python中可变命名元组的存在?”

0

基于多年积累的一些有用技巧,这个“frozenclass”装饰器几乎可以完成所有需要的功能:http://pastebin.com/fsuVyM45

由于该代码超过70%的文档和测试,我在此不再赘述。


0

这是我制作的一个完整的可变命名元组,它的行为类似于列表,并且与之完全兼容。

class AbstractNamedArray():
    """a mutable collections.namedtuple"""
    def __new__(cls, *args, **kwargs):
        inst = object.__new__(cls)  # to rename the class
        inst._list = len(cls._fields)*[None]
        inst._mapping = {}
        for i, field in enumerate(cls._fields):
            inst._mapping[field] = i
        return inst

    def __init__(self, *args, **kwargs):
        if len(kwargs) == 0 and len(args) != 0:
            assert len(args) == len(self._fields), 'bad number of arguments'
            self._list = list(args)
        elif len(args) == 0 and len(kwargs) != 0:
            for field, value in kwargs.items():
                assert field in self._fields, 'field {} doesn\'t exist'
                self._list[self._mapping[field]] = value
        else:
            raise ValueError("you can't mix args and kwargs")

    def __getattr__(self, x):
        return object.__getattribute__(self, '_list')[object.__getattribute__(self, '_mapping')[x]]

    def __setattr__(self, x, y):
        if x in self._fields:
            self._list[self._mapping[x]] = y
        else:
            object.__setattr__(self, x, y)

    def __repr__(self):
        fields = []
        for field, value in zip(self._fields, map(self.__getattr__, self._fields)):
            fields.append('{}={}'.format(field, repr(value)))
        return '{}({})'.format(self._name, ', '.join(fields))

    def __iter__(self):
        yield from self._list

    def __list__(self):
        return self._list[:]

    def __len__(self):
        return len(self._fields)

    def __getitem__(self, x):
        return self._list[x]

    def __setitem__(self, x, y):
        self._list[x] = y

    def __contains__(self, x):
        return x in self._list

    def reverse(self):
        self._list.reverse()

    def copy(self):
        return self._list.copy()


def namedarray(name, fields):
    """used to construct a named array (fixed-length list with named fields)"""
    return type(name, (AbstractNamedarray,), {'_name': name, '_fields': fields})

0
你可以创建一个类似于dict的子类,它有自己的__dict__。基本概念与ActiveState AttrDict配方相同,但实现更简单。结果是比你需要的更可变,因为实例的属性和它们的值都是可变的。虽然这些属性没有顺序,但你可以遍历当前属性及其值。
class Record(dict):
    def __init__(self, *args, **kwargs):
        super(Record, self).__init__(*args, **kwargs)
        self.__dict__ = self

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