在Python中是否存在可变的命名元组?

Ale*_*der 105 python mutable namedtuple

任何人都可以修改namedtuple或提供替代类,以便它适用于可变对象吗?

主要是为了可读性,我想要类似于namedtuple这样做:

from Camelot import namedgroup

Point = namedgroup('Point', ['x', 'y'])
p = Point(0, 0)
p.x = 10

>>> p
Point(x=10, y=0)

>>> p.x *= 10
Point(x=100, y=0)
Run Code Online (Sandbox Code Playgroud)

必须可以腌制生成的对象.并且根据命名元组的特征,当表示时,输出的排序必须与构造对象时参数列表的顺序相匹配.

int*_*ath 113

有一个可变的替代collections.namedtuple- recordclass.

它具有相同的API和内存占用,namedtuple并且它支持分配(它也应该更快).例如:

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)
Run Code Online (Sandbox Code Playgroud)

对于python 3.6及更高版本recordclass(自0.5起)支持typehints:

from recordclass import recordclass, RecordClass

class Point(RecordClass):
   x: int
   y: int

>>> Point.__annotations__
{'x':int, 'y':int}
>>> 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)
Run Code Online (Sandbox Code Playgroud)

有一个更完整的例子(它还包括性能比较).

由于0.9 recordclass库提供了另一种变体 - recordclass.structclass工厂功能.它可以生成类,其实例占用的内存少于__slots__基于实例的内存.这对于具有属性值的实例非常重要,属性值并非旨在具有参考周期.这是一个说明性的例子.

  • 喜欢它。“这个库实际上是命名元组的“可变”替代问题的“概念证明”。 (3认同)

fun*_*ure 27

types.SimpleNamespace是在Python 3.3中引入的,它支持所请求的要求.

from types import SimpleNamespace
t = SimpleNamespace(foo='bar')
t.ham = 'spam'
print(t)
namespace(foo='bar', ham='spam')
print(t.foo)
'bar'
import pickle
with open('/tmp/pickle', 'wb') as f:
    pickle.dump(t, f)
Run Code Online (Sandbox Code Playgroud)

  • -1 OP 在他的测试中非常清楚他需要什么,并且 `SimpleNamespace` 未能通过测试 6-10(按索引访问、迭代解包、迭代、有序字典、就地替换)和 12、13(字段、插槽) . 请注意,文档(您在答案中链接)特别指出 *“`SimpleNamespace` 可能有助于替代 `class NS: pass`。但是,对于结构化记录类型,请改用 `namedtuple()`。”* (4认同)
  • 多年来我一直在寻找这样的东西。点图等点式字典库的绝佳替代品 (2认同)
  • 这需要更多的赞成票。这正是 OP 正在寻找的东西,它在标准库中,使用起来再简单不过了。谢谢! (2认同)
  • -1 也是,`SimpleNamespace` 创建一个对象,而不是类构造函数,并且不能替代 nametuple。类型比较不起作用,并且内存占用会高很多。 (2认同)

ken*_*nes 23

似乎这个问题的答案是否定的.

下面非常接近,但它在技术上并不可变.这是创建一个namedtuple()具有更新x值的新实例:

Point = namedtuple('Point', ['x', 'y'])
p = Point(0, 0)
p = p._replace(x=10) 
Run Code Online (Sandbox Code Playgroud)

另一方面,您可以创建一个简单的类__slots__,它应该适用于频繁更新类实例属性:

class Point:
    __slots__ = ['x', 'y']
    def __init__(self, x, y):
        self.x = x
        self.y = y
Run Code Online (Sandbox Code Playgroud)

为了添加这个答案,我认为__slots__这里很好用,因为在创建大量类实例时它的内存效率很高.唯一的缺点是您无法创建新的类属性.

这是一个相关的线程,说明了内存效率 - 字典与对象 - 哪个更有效,为什么?

这个帖子的答案中引用的内容是一个非常简洁的解释,为什么__slots__更高效的内存 - Python插槽


Ali*_*Ali 23

最新的命名列表 1.7在 2016 1月11日之前通过Python 2.7和Python 3.5传递所有测试.它是一个纯python实现,而它recordclass是一个C扩展.当然,这取决于您的要求是否首选C扩展名.

你的测试(但也见下面的注释):

from __future__ import print_function
import pickle
import sys
from namedlist import namedlist

Point = namedlist('Point', 'x y')
p = Point(x=1, y=2)

print('1. Mutation of field values')
p.x *= 10
p.y += 10
print('p: {}, {}\n'.format(p.x, p.y))

print('2. String')
print('p: {}\n'.format(p))

print('3. Representation')
print(repr(p), '\n')

print('4. Sizeof')
print('size of p:', sys.getsizeof(p), '\n')

print('5. Access by name of field')
print('p: {}, {}\n'.format(p.x, p.y))

print('6. Access by index')
print('p: {}, {}\n'.format(p[0], p[1]))

print('7. Iterative unpacking')
x, y = p
print('p: {}, {}\n'.format(x, y))

print('8. Iteration')
print('p: {}\n'.format([v for v in p]))

print('9. Ordered Dict')
print('p: {}\n'.format(p._asdict()))

print('10. Inplace replacement (update?)')
p._update(x=100, y=200)
print('p: {}\n'.format(p))

print('11. Pickle and Unpickle')
pickled = pickle.dumps(p)
unpickled = pickle.loads(pickled)
assert p == unpickled
print('Pickled successfully\n')

print('12. Fields\n')
print('p: {}\n'.format(p._fields))

print('13. Slots')
print('p: {}\n'.format(p.__slots__))
Run Code Online (Sandbox Code Playgroud)

Python 2.7上的输出

1. Mutation of field values  
p: 10, 12

2. String  
p: Point(x=10, y=12)

3. Representation  
Point(x=10, y=12) 

4. Sizeof  
size of p: 64 

5. Access by name of field  
p: 10, 12

6. Access by index  
p: 10, 12

7. Iterative unpacking  
p: 10, 12

8. Iteration  
p: [10, 12]

9. Ordered Dict  
p: OrderedDict([('x', 10), ('y', 12)])

10. Inplace replacement (update?)  
p: Point(x=100, y=200)

11. Pickle and Unpickle  
Pickled successfully

12. Fields  
p: ('x', 'y')

13. Slots  
p: ('x', 'y')

与Python 3.5的唯一区别在于namedlist它变小了,大小为56(Python 2.7报告64).

请注意,我已将测试10更改为就地更换.namedlist_replace()哪些做了浅拷贝的方法,这使我感觉良好,因为namedtuple在标准库的工作方式.更改_replace()方法的语义会令人困惑.在我看来,该_update()方法应该用于就地更新.或者我可能没理解你的测试10的意图?

  • 匿名小票对任何人都没有帮助。答案有什么问题?为什么要投票? (2认同)

Kas*_*mvd 16

作为此任务的非常Pythonic替代方案,从Python-3.7开始,您可以使用 dataclasses不仅行为像mutable一样的模块,NamedTuple因为它们使用普通的类定义,它们也支持其他类的特性.

从PEP-0557:

尽管它们使用了一种非常不同的机制,但数据类可以被认为是"具有默认值的可变命名元组".因为数据类使用普通的类定义语法,所以您可以自由地使用继承,元类,文档字符串,用户定义的方法,类工厂和其他Python类功能.

提供了一个类装饰器,它检查具有类型注释的变量的类定义,如PEP 526 "变量注释的语法"中所定义.在本文档中,此类变量称为字段.使用这些字段,装饰器将生成的方法定义添加到类中以支持实例初始化,repr,比较方法以及" 规范"部分中描述的可选的其他方法.这样的类被称为数据类,但该类没有什么特别之处:装饰器将生成的方法添加到类中并返回给定的类.

PEP-0557中引入了此功能,您可以在提供的文档链接中更详细地了解它.

例:

In [20]: from dataclasses import dataclass

In [21]: @dataclass
    ...: class InventoryItem:
    ...:     '''Class for keeping track of an item in inventory.'''
    ...:     name: str
    ...:     unit_price: float
    ...:     quantity_on_hand: int = 0
    ...: 
    ...:     def total_cost(self) -> float:
    ...:         return self.unit_price * self.quantity_on_hand
    ...:    
Run Code Online (Sandbox Code Playgroud)

演示:

In [23]: II = InventoryItem('bisc', 2000)

In [24]: II
Out[24]: InventoryItem(name='bisc', unit_price=2000, quantity_on_hand=0)

In [25]: II.name = 'choco'

In [26]: II.name
Out[26]: 'choco'

In [27]: 

In [27]: II.unit_price *= 3

In [28]: II.unit_price
Out[28]: 6000

In [29]: II
Out[29]: InventoryItem(name='choco', unit_price=6000, quantity_on_hand=0)
Run Code Online (Sandbox Code Playgroud)

  • 虽然这可能不是OP想要的,但它确实对我有帮助:) (4认同)
  • OP 中的测试非常清楚需要什么,并且“dataclass”未通过测试 6-10(通过索引访问、迭代解包、迭代、有序字典、就地替换)和 12、13(字段、槽)在 Python 3.7.1 中。 (2认同)

Ant*_*ala 6

以下是Python 3的一个很好的解决方案:使用最小类__slots__Sequence抽象基类; 不会做花哨的错误检测等,但它可以工作,并且行为大多像一个可变的元组(除了类型检查).

from collections import Sequence

class NamedMutableSequence(Sequence):
    __slots__ = ()

    def __init__(self, *a, **kw):
        slots = self.__slots__
        for k in slots:
            setattr(self, k, kw.get(k))

        if a:
            for k, v in zip(slots, a):
                setattr(self, k, v)

    def __str__(self):
        clsname = self.__class__.__name__
        values = ', '.join('%s=%r' % (k, getattr(self, k))
                           for k in self.__slots__)
        return '%s(%s)' % (clsname, values)

    __repr__ = __str__

    def __getitem__(self, item):
        return getattr(self, self.__slots__[item])

    def __setitem__(self, item, value):
        return setattr(self, self.__slots__[item], value)

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

class Point(NamedMutableSequence):
    __slots__ = ('x', 'y')
Run Code Online (Sandbox Code Playgroud)

例:

>>> p = Point(0, 0)
>>> p.x = 10
>>> p
Point(x=10, y=0)
>>> p.x *= 10
>>> p
Point(x=100, y=0)
Run Code Online (Sandbox Code Playgroud)

如果需要,您也可以使用方法来创建类(尽管使用显式类更透明):

def namedgroup(name, members):
    if isinstance(members, str):
        members = members.split()
    members = tuple(members)
    return type(name, (NamedMutableSequence,), {'__slots__': members})
Run Code Online (Sandbox Code Playgroud)

例:

>>> Point = namedgroup('Point', ['x', 'y'])
>>> Point(6, 42)
Point(x=6, y=42)
Run Code Online (Sandbox Code Playgroud)

在Python 2,你需要稍微调整它-如果你继承Sequence,类将有一个__dict____slots__将停止工作.

Python 2中的解决方案是不继承Sequence,但是object.如果isinstance(Point, Sequence) == True需要,您需要将NamedMutableSequence作为基类注册 到Sequence:

Sequence.register(NamedMutableSequence)
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

28145 次

最近记录:

6 年,6 月 前