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

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

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

from Camelot import namedgroup Point = namedgroup('Point', ['x', 'y']) p = Point(0, 0) px = 10 >>> p Point(x=10, y=0) >>> px *= 10 Point(x=100, y=0) 

必须可以腌制所得到的物体。 并且,根据命名元组的特性,当表示时,输出的sorting必须与构造对象时参数列表的顺序相匹配。

回复:感谢所有提交了build议的人。 我相信@intellimath提到的logging类是最好的解决scheme( 在这里也可以看到)。

recordclass 0.4 collections.namedtple的可变变体,支持赋值

recordclass是MIT授权的python库。 它实现了typesmemoryslots和工厂函数logging类,以创build类似logging的类。

memoryslots是元组types,它支持分配操作。 recordclass是一个工厂函数,用于创buildcollection.namedtple的“可变”模拟。 这个库实际上是一个“可变的”替代名称的问题的“概念certificate”。

我也对所有的build议进行了一些testing。 并不是所有这些function都被要求,所以比较并不公平。 这里的testing只是为了指出每个类的可用性。

 # Option 1 (p1): @kennes913 # Option 2 (p2): @MadMan2064 # Option 3 (p3): @intellimath # Option 4 (p4): @Roland Smith # Option 5 (p5): @agomcas # Option 6 (p6): @Antti Haapala # TEST: p1 p2 p3 p4 p5 p6 # 1. Mutation of field values | x | x | x | x | x | x | # 2. String | | x | x | x | | x | # 3. Representation | | x | x | x | | x | # 4. Sizeof | x | x | x | ? | ?? | x | # 5. Access by name of field | x | x | x | x | x | x | # 6. Access by index. | | | x | | | | # 7. Iterative unpacking. | | x | x | | | x | # 8. Iteration | | x | x | | | x | # 9. Ordered Dict | | | x | | | | # 10. Inplace replacement | | | x | | | | # 11. Pickle and Unpickle | | | x | | | | # 12. Fields* | | | yes | | yes | | # 13. Slots* | yes | | | | yes | | # *Note that I'm not very familiar with slots and fields, so please excuse # my ignorance in reporting their results. I have included them for completeness. # Class/Object creation. p1 = Point1(x=1, y=2) Point2 = namedgroup("Point2", ["x", "y"]) p2 = Point2(x=1, y=2) Point3 = recordclass('Point3', 'x y') # *** p3 = Point3(x=1, y=2) p4 = AttrDict() p4.x = 1 p4.y = 2 p5 = namedlist('Point5', 'x y') Point6 = namedgroup('Point6', ['x', 'y']) p6 = Point6(x=1, y=2) point_objects = [p1, p2, p3, p4, p5, p6] # 1. Mutation of field values. for n, p in enumerate(point_objects): try: px *= 10 py += 10 print('p{0}: {1}, {2}'.format(n + 1, px, py)) except Exception as e: print('p{0}: Mutation not supported. {1}'.format(n + 1, e)) p1: 10, 12 p2: 10, 12 p3: 10, 12 p4: 10, 12 p5: 10, 12 p6: 10, 12 # 2. String. for n, p in enumerate(point_objects): print('p{0}: {1}'.format(n + 1, p)) p1: <__main__.Point1 instance at 0x10c72dc68> p2: Point2(x=10, y=12) p3: Point3(x=10, y=12) p4: {'y': 12, 'x': 10} p5: <class '__main__.Point5'> p6: Point6(x=10, y=12) # 3. Representation. [('p{0}'.format(n + 1), p) for n, p in enumerate(point_objects)] [('p1', <__main__.Point1 instance at 0x10c72dc68>), ('p2', Point2(x=10, y=12)), ('p3', Point3(x=10, y=12)), ('p4', {'x': 10, 'y': 12}), ('p5', __main__.Point5), ('p6', Point6(x=10, y=12))] # 4. Sizeof. for n, p in enumerate(point_objects): print("size of p{0}:".format(n + 1), sys.getsizeof(p)) size of p1: 72 size of p2: 64 size of p3: 72 size of p4: 280 size of p5: 904 size of p6: 64 # 5. Access by name of field. for n, p in enumerate(point_objects): print('p{0}: {1}, {2}'.format(n + 1, px, py)) p1: 10, 12 p2: 10, 12 p3: 10, 12 p4: 10, 12 p5: 10, 12 p6: 10, 12 # 6. Access by index. for n, p in enumerate(point_objects): try: print('p{0}: {1}, {2}'.format(n + 1, p[0], p[1])) except: print('p{0}: Unable to access by index.'.format(n+1)) p1: Unable to access by index. p2: Unable to access by index. p3: 10, 12 p4: Unable to access by index. p5: Unable to access by index. p6: Unable to access by index. # 7. Iterative unpacking. for n, p in enumerate(point_objects): try: x, y = p print('p{0}: {1}, {2}'.format(n + 1, x, y)) except: print('p{0}: Unable to unpack.'.format(n + 1)) p1: Unable to unpack. p2: 10, 12 p3: 10, 12 p4: y, x p5: Unable to unpack. p6: 10, 12 # 8. Iteration for n, p in enumerate(point_objects): try: print('p{0}: {1}'.format(n + 1, [v for v in p])) except: print('p{0}: Unable to iterate.'.format(n + 1)) p1: Unable to iterate. p2: [10, 12] p3: [10, 12] p4: ['y', 'x'] p5: Unable to iterate. p6: [10, 12] In [95]: # 9. Ordered Dict for n, p in enumerate(point_objects): try: print('p{0}: {1}'.format(n + 1, p._asdict())) except: print('p{0}: Unable to create Ordered Dict.'.format(n + 1)) p1: Unable to create Ordered Dict. p2: Unable to create Ordered Dict. p3: OrderedDict([('x', 10), ('y', 12)]) p4: Unable to create Ordered Dict. p5: Unable to create Ordered Dict. p6: Unable to create Ordered Dict. # 10. Inplace replacement for n, p in enumerate(point_objects): try: p_ = p._replace(x=100, y=200) print('p{0}: {1} - {2}'.format(n + 1, 'Success' if p is p_ else 'Failure', p)) except: print('p{0}: Unable to replace inplace.'.format(n + 1)) p1: Unable to replace inplace. p2: Unable to replace inplace. p3: Success - Point3(x=100, y=200) p4: Unable to replace inplace. p5: Unable to replace inplace. p6: Unable to replace inplace. # 11. Pickle and Unpickle. for n, p in enumerate(point_objects): try: pickled = pickle.dumps(p) unpickled = pickle.loads(pickled) if p != unpickled: raise ValueError((p, unpickled)) print('p{0}: {1}'.format(n + 1, 'Pickled successfully', )) except Exception as e: print('p{0}: {1}; {2}'.format(n + 1, 'Pickle failure', e)) p1: Pickle failure; (<__main__.Point1 instance at 0x10c72dc68>, <__main__.Point1 instance at 0x10ca631b8>) p2: Pickle failure; (Point2(x=10, y=12), Point2(x=10, y=12)) p3: Pickled successfully p4: Pickle failure; '__getstate__' p5: Pickle failure; Can't pickle <class '__main__.Point5'>: it's not found as __main__.Point5 p6: Pickle failure; (Point6(x=10, y=12), Point6(x=10, y=12)) # 12. Fields. for n, p in enumerate(point_objects): try: print('p{0}: {1}'.format(n + 1, p._fields)) except Exception as e: print('p{0}: {1}; {2}'.format(n + 1, 'Unable to access fields.', e)) p1: Unable to access fields.; Point1 instance has no attribute '_fields' p2: Unable to access fields.; 'Point2' object has no attribute '_fields' p3: ('x', 'y') p4: Unable to access fields.; '_fields' p5: ('x', 'y') p6: Unable to access fields.; 'Point6' object has no attribute '_fields' # 13. Slots. for n, p in enumerate(point_objects): try: print('p{0}: {1}'.format(n + 1, p.__slots__)) except Exception as e: print('p{0}: {1}; {2}'.format(n + 1, 'Unable to access slots', e)) p1: ['x', 'y'] p2: Unable to access slots; 'Point2' object has no attribute '__slots__' p3: () p4: Unable to access slots; '__slots__' p5: ('x', 'y') p6: Unable to access slots; 'Point6' object has no attribute '__slots__' 

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

它具有与namedtuple相同的API和内存占用,并支持赋值(它也应该更快)。 例如:

 from recordclass import recordclass Point = recordclass('Point', 'x y') >>> p = Point(1, 2) >>> p Point(x=1, y=2) >>> print(px, py) 1 2 >>> px += 2; py += 3; print(p) Point(x=3, y=5) 

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

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

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

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

另一方面,您可以使用__slots__创build一个简单的类,以便经常更新类实例属性:

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

要添加到这个答案,我认为__slots__在这里很好用,因为当你创build大量的类实例时,它是有效的。 唯一的缺点是你不能创build新的类属性。

下面是一个说明内存效率的相关线程 – Dictionary vs Object – 哪个更有效率,为什么?

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

最新的namedlist 1.7从 2016 1月11日起将Python 2.7和Python 3.5的所有testing都通过了它是一个纯Python实现,recordclass是C扩展。 当然,这取决于你的要求是否C扩展是首选的。

你的testing(但也看到下面的说明):

 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') px *= 10 py += 10 print('p: {}, {}\n'.format(px, py)) 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(px, py)) 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__)) 

在Python 2.7输出

 1.字段值的变异  
 p:10,12

 2.string  
 p:点(x = 10,y = 12)

 3.表示  
点(x = 10,y = 12) 

 4.尺寸  
大小p:64 

 5.按字段名称访问  
 p:10,12

 6.按索引访问  
 p:10,12

 7.迭代拆包  
 p:10,12

 8.迭代  
 p:[10,12]

 9.有序字典  
 p:OrderedDict([('x',10),('y',12)])

 10.就地更换(更新?)  
 p:点(x = 100,y = 200)

 11.泡菜和Unpickle  
腌制成功

 12.领域  
 p:('x','y')

 13.插槽  
 p:('x','y')

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

请注意,我已经更改了您的testing10以进行就地更换。 namedlist有一个_replace()方法,它做一个浅拷贝,这对我来说是完全有意义的,因为标准库中namedtuple的行为是一样的。 改变_replace()方法的语义会令人困惑。 在我看来, _update()方法应该用于就地更新。 或者,也许我不明白你的testing10的意图?

以下是Python 3的一个很好的解决scheme:使用__slots__Sequence抽象基类的最小类; 不会做花哨的错误检测等,但它的工作原理,大部分行为像一个可变的元组(typecheck除外)。

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

例:

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

如果你愿意,你可以有一个方法来创build类(尽pipe使用一个显式类更透明):

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

例:

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

在Python 2中,您需要稍微调整它 – 如果从Sequenceinheritance,则类将有__dict____slots__将停止工作。

Python 2中的解决scheme不是从Sequenceinheritance,而是inheritanceobject 。 如果isinstance(Point, Sequence) == True则需要将NamedMutableSequence作为基类注册到Sequence

 Sequence.register(NamedMutableSequence) 

我们用dynamictypes创build来实现这个:

 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) 

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

那么这是可以腌制的吗? 是的,如果(并且只有)你做以下事情:

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

定义必须在你的命名空间中,并且必须存在足够长的时间才能find它。 所以如果你把它定义在你的包中,它应该可以工作。

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

如果你做了下面的事情,Pickle就会失败,或者把定义定义为临时的(当函数结束的时候超出范围):

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

是的,它确实保留了types创build中列出的字段的顺序。

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) 

“`

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

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

 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} 

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

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

提供的性能不重要,可以使用一个愚蠢的黑客,如:

 from collection import namedtuple Point = namedtuple('Point', 'xy z') mutable_z = Point(1,2,[3])