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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29290359/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 04:20:07  来源:igfitidea点击:

Existence of mutable named tuple in Python?

pythonmutablenamedtuple

提问by Alexander

Can anyone amend namedtupleor provide an alternative class so that it works for mutable objects?

任何人都可以修改namedtuple或提供一个替代类以使其适用于可变对象吗?

Primarily for readability, I would like something similar to namedtuple that does this:

主要是为了可读性,我想要类似于 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)

It must be possible to pickle the resulting object. And per the characteristics of named tuple, the ordering of the output when represented must match the order of the parameter list when constructing the object.

必须可以对结果对象进行pickle。并且根据命名元组的特性,输出的表示顺序必须与构造对象时参数列表的顺序相匹配。

采纳答案by intellimath

There is a mutable alternative to collections.namedtuple- recordclass.

还有就是一个可变的替代方案collections.namedtuple- recordclass

It has the same API and memory footprint as namedtupleand it supports assignments (It should be faster as well). For example:

它具有相同的 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)

For python 3.6 and higher recordclass(since 0.5) support typehints:

对于 python 3.6 及更高版本recordclass(从 0.5 开始)支持类型提示:

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)

There is a more complete example(it also includes performance comparisons).

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

Since 0.9 recordclasslibrary provides another variant -- recordclass.structclassfactory function. It can produce classes, whose instances occupy less memory than __slots__-based instances. This is can be important for the instances with attribute values, which has not intended to have reference cycles. It may help reduce memory usage if you need to create millions of instances. Here is an illustrative example.

由于 0.9recordclass库提供了另一个变体——recordclass.structclass工厂函数。它可以生成类,其实例比__slots__基于实例占用更少的内存。这对于具有属性值的实例可能很重要,这些实例不打算具有引用循环。如果您需要创建数百万个实例,它可能有助于减少内存使用。这是一个说明性的例子

回答by kennes

It seems like the answer to this question is no.

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

Below is pretty close, but it's not technically mutable. This is creating a new namedtuple()instance with an updated x value:

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

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

On the other hand, you can create a simple class using __slots__that should work well for frequently updating class instance attributes:

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

class Point:
    __slots__ = ['x', 'y']
    def __init__(self, x, y):
        self.x = x
        self.y = y

To add to this answer, I think __slots__is good use here because it's memory efficient when you create lots of class instances. The only downside is that you can't create new class attributes.

要补充这个答案,我认为__slots__这里很好用,因为当您创建大量类实例时,它的内存效率很高。唯一的缺点是您不能创建新的类属性。

Here's one relevant thread that illustrates the memory efficiency - Dictionary vs Object - which is more efficient and why?

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

The quoted content in the answer of this thread is a very succinct explanation why __slots__is more memory efficient - Python slots

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

回答by Roland Smith

Tuples are by definition immutable.

元组根据定义是不可变的。

You can however make a dictionary subclass where you can access the attributes with dot-notation;

但是,您可以创建一个字典子类,您可以在其中使用点符号访问属性;

In [1]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:class AttrDict(dict):
:
:    def __getattr__(self, name):
:        return self[name]
:
:    def __setattr__(self, name, value):
:        self[name] = value
:--

In [2]: test = AttrDict()

In [3]: test.a = 1

In [4]: test.b = True

In [5]: test
Out[5]: {'a': 1, 'b': True}

回答by agomcas

If you want similar behavior as namedtuples but mutable try namedlist

如果您想要与namedtuples类似但可变的行为,请尝试namedlist

Note that in order to be mutable it cannotbe a tuple.

请注意,为了可变,它不能是元组。

回答by MadMan2064

Let's implement this with dynamic type creation:

让我们通过动态类型创建来实现:

import copy
def namedgroup(typename, fieldnames):

    def init(self, **kwargs): 
        attrs = {k: None for k in self._attrs_}
        for k in kwargs:
            if k in self._attrs_:
                attrs[k] = kwargs[k]
            else:
                raise AttributeError('Invalid Field')
        self.__dict__.update(attrs)

    def getattribute(self, attr):
        if attr.startswith("_") or attr in self._attrs_:
            return object.__getattribute__(self, attr)
        else:
            raise AttributeError('Invalid Field')

    def setattr(self, attr, value):
        if attr in self._attrs_:
            object.__setattr__(self, attr, value)
        else:
            raise AttributeError('Invalid Field')

    def rep(self):
         d = ["{}={}".format(v,self.__dict__[v]) for v in self._attrs_]
         return self._typename_ + '(' + ', '.join(d) + ')'

    def iterate(self):
        for x in self._attrs_:
            yield self.__dict__[x]
        raise StopIteration()

    def setitem(self, *args, **kwargs):
        return self.__dict__.__setitem__(*args, **kwargs)

    def getitem(self, *args, **kwargs):
        return self.__dict__.__getitem__(*args, **kwargs)

    attrs = {"__init__": init,
                "__setattr__": setattr,
                "__getattribute__": getattribute,
                "_attrs_": copy.deepcopy(fieldnames),
                "_typename_": str(typename),
                "__str__": rep,
                "__repr__": rep,
                "__len__": lambda self: len(fieldnames),
                "__iter__": iterate,
                "__setitem__": setitem,
                "__getitem__": getitem,
                }

    return type(typename, (object,), attrs)

This checks the attributes to see if they are valid before allowing the operation to continue.

这会在允许操作继续之前检查属性以查看它们是否有效。

So is this pickleable? Yes if (and only if) you do the following:

那么这个可以腌制吗?是的,如果(且仅当)您执行以下操作:

>>> import pickle
>>> Point = namedgroup("Point", ["x", "y"])
>>> p = Point(x=100, y=200)
>>> p2 = pickle.loads(pickle.dumps(p))
>>> p2.x
100
>>> p2.y
200
>>> id(p) != id(p2)
True

The definition has to be in your namespace, and must exist long enough for pickle to find it. So if you define this to be in your package, it should work.

该定义必须在您的命名空间中,并且必须存在足够长的时间以便 pickle 找到它。因此,如果您将其定义在您的包中,它应该可以工作。

Point = namedgroup("Point", ["x", "y"])

Pickle will fail if you do the following, or make the definition temporary (goes out of scope when the function ends, say):

如果您执行以下操作,或者使定义临时(例如,函数结束时超出范围),Pickle 将失败:

some_point = namedgroup("Point", ["x", "y"])

And yes, it does preserve the order of the fields listed in the type creation.

是的,它确实保留了类型创建中列出的字段的顺序。

回答by Antti Haapala

The following is a good solution for Python 3: A minimal class using __slots__and Sequenceabstract base class; does not do fancy error detection or such, but it works, and behaves mostly like a mutable tuple (except for typecheck).

以下是 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')

Example:

例子:

>>> p = Point(0, 0)
>>> p.x = 10
>>> p
Point(x=10, y=0)
>>> p.x *= 10
>>> p
Point(x=100, y=0)

If you want, you can have a method to create the class too (though using an explicit class is more transparent):

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

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

Example:

例子:

>>> Point = namedgroup('Point', ['x', 'y'])
>>> Point(6, 42)
Point(x=6, y=42)


In Python 2 you need to adjust it slightly - if you inherit from Sequence, the class will have a __dict__and the __slots__will stop from working.

在 Python 2 中,您需要稍微调整它 - 如果您继承自Sequence,则该类将具有 a__dict__并且__slots__将停止工作。

The solution in Python 2 is to not inherit from Sequence, but object. If isinstance(Point, Sequence) == Trueis desired, you need to register the NamedMutableSequenceas a base class to Sequence:

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

Sequence.register(NamedMutableSequence)

回答by Ali

The latest namedlist1.7 passes all of your tests with both Python 2.7 and Python 3.5as of Jan 11, 2016. It is a pure python implementationwhereas the recordclassis a C extension. Of course, it depends on your requirements whether a C extension is preferred or not.

截至 20161 月 11 日,最新的namedlist1.7 通过了 Python 2.7 和 Python 3.5 的所有测试它是一个纯 Python 实现,而它recordclass是一个 C 扩展。当然,是否首选 C 扩展取决于您的要求。

Your tests (but also see the note below):

您的测试(但也请参阅下面的注释):

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

Output on Python 2.7

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

The only difference with Python 3.5 is that the namedlisthas become smaller, the size is 56 (Python 2.7 reports 64).

与 Python 3.5 唯一的区别是namedlist变小了,大小为 56(Python 2.7 报告为 64)。

Note that I have changed your test 10 for in-place replacement.The namedlisthas a _replace()method which does a shallow copy, and that makes perfect sense to me because the namedtuplein the standard library behaves the same way. Changing the semantics of the _replace()method would be confusing. In my opinion the _update()method should be used for in-place updates. Or maybe I failed to understand the intent of your test 10?

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

回答by funky-future

types.SimpleNamespacewas introduced in Python 3.3 and supports the requested requirements.

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)

回答by Srg

Provided performance is of little importance, one could use a silly hack like:

如果性能无关紧要,可以使用以下愚蠢的 hack 方法:

from collection import namedtuple

Point = namedtuple('Point', 'x y z')
mutable_z = Point(1,2,[3])

回答by Kasramvd

As a very Pythonic alternative for this task, since Python-3.7, you can use dataclassesmodule that not only behaves like a mutable NamedTuplebecause they use normal class definitions they also support other classes features.

作为此任务的一个非常 Pythonic 的替代方案,从 Python-3.7 开始,您可以使用 dataclasses不仅表现得像可变的模块,NamedTuple因为它们使用普通的类定义,而且还支持其他类功能。

From PEP-0557:

来自 PEP-0557:

Although they use a very different mechanism, Data Classes can be thought of as "mutable namedtuples with defaults". Because Data Classes use normal class definition syntax, you are free to use inheritance, metaclasses, docstrings, user-defined methods, class factories, and other Python class features.

A class decorator is provided which inspects a class definition for variables with type annotations as defined in PEP 526, "Syntax for Variable Annotations". In this document, such variables are called fields. Using these fields, the decorator adds generated method definitions to the class to support instance initialization, a repr, comparison methods, and optionally other methods as described in the Specificationsection. Such a class is called a Data Class, but there's really nothing special about the class: the decorator adds generated methods to the class and returns the same class it was given.

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

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

This feature is introduced in PEP-0557that you can read about it in more details on provided documentation link.

此功能在PEP-0557中引入,您可以在提供的文档链接上阅读更多详细信息。

Example:

例子:

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
    ...:    

Demo:

演示:

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)