Existence of a modified tuple name in Python?

Can someone modify namedtuple or provide an alternative class so that it works for mutable objects?

First of all, for readability, I would like something similar to namedtuple, which does this:

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) 

It should be possible to salt the resulting object. And according to the characteristics of the named tuple, the output order of the output, when presented, should match the order of the parameter list when constructing the object.

+89
python mutable namedtuple
Mar 26 '15 at 22:56
source share
11 answers

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

It has the same API and memory size as namedtuple and supports assignments (it should also be faster). For example:

 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) 

For python 3.6 and later recordclass (starting from 0.5) fonts are supported:

 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(px, py) 1 2 >>> px += 2; py += 3; print(p) Point(x=3, y=5) 

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

Starting with recordclass 0.9, the recordclass library provides another option - the recordclass.structclass function of the recordclass.structclass function. It can create classes whose instances take up less memory than __slots__ -based. This may be important for instances with attribute values ​​that should not have reference loops. This can help reduce memory usage if you need to create millions of instances. Here is a good example .

+92
Apr 02 '15 at 18:16
source share
— -

The answer seems to be no.

Below is pretty close, but it's not technically volatile. This creates a new instance of namedtuple() with the updated x value:

 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__ , which should work well to frequently update the attributes of an instance of a class:

 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 useful here because it is effective in creating a large number of class instances. The only drawback is that you cannot create new class attributes.

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

The recorded content in response to this stream is a very brief explanation of why __slots__ more memory efficient - Python slots

+21
Mar 26 '15 at 23:09
source share

The latest namedlist 1.7 passes all your tests with both Python 2.7 and Python 3.5 from January 11, 2016. This is a pure python implementation , whereas recordclass is an extension of C. Of course, it depends on your requirements, whether the extension C is preferred or not.

Your tests (but also see 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') 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 Output

 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 from Python 3.5 is that the size of namedlist become smaller, the size is 56 (Python 2.7 reports 64).

Please note that I changed your test 10 to an in-place replacement. namedlist has a _replace() method that makes a shallow copy, and that makes perfect sense for me because namedtuple behaves the same in the standard library. Changing the semantics of the _replace() method will be confusing. In my opinion, the _update() method should be used to update in-place. Or maybe I did not understand the purpose of your test 10?

+19
Jan 11 '16 at 14:25
source share

types.SimpleNamespace was introduced in Python 3.3 and supports the requested requirements.

 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) 
+17
Oct 30 '16 at 15:15
source share

As a very Pythonic alternative for this task, starting with Python-3.7, you can use the dataclasses module dataclasses which not only behaves like a mutable NamedTuple because they use the usual class definitions, but also support other class functions.

From PEP-0557:

Although data classes use a completely different mechanism, they can be thought of as "mutable named tuples with default values." Because data classes use the usual class definition syntax, you can freely use inheritance, metaclasses, documentation strings, user-defined methods, class factories, and other Python class functions.

A class decorator is provided that checks the 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 the generated method definitions to the class to support instance initialization, repr, comparison methods, and, if necessary, other methods, as described in the Specification section. Such a class is called a data class, but there is nothing special about it: the decorator adds the generated methods to the class and returns the same class that was provided to it.

This function is presented in PEP-0557, which you can read about in more detail at the provided link to the documentation.

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

 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) 
+8
Apr 17 '18 at 9:39
source share

The following is a good solution for Python 3: a minimal class using the base class __slots__ and Sequence ; doesn't make fancy error detection or such, but it works and behaves basically like a mutable tuple (except for 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') 

Example:

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

If you want, you can also create a method to create a class (although 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 a bit - if you inherit from Sequence , the class will have __dict__ , and __slots__ will stop working.

The solution in Python 2 is not inherited from Sequence , but object . If isinstance(Point, Sequence) == True is required, you need to register NamedMutableSequence as the base class for Sequence :

 Sequence.register(NamedMutableSequence) 
+6
Apr 3 '15 at 11:01
source share

Let me implement this with the creation of a dynamic type:

 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 make sure they are valid before allowing the operation to continue.

So is it legible? 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 must be in your namespace and must exist long enough to breed to find it. Therefore, if you defined this in your package, it should work.

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

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

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

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

+3
Apr 01 '15 at 7:57
source share

If you want similar behavior like namedtuples but mutable try namedlist

Note that in order to be volatile, it cannot be a tuple.

+2
Mar 31 '15 at 8:54
source share

Tuples are, by definition, immutable.

However, you can create a subclass of the dictionary where you can access attributes using 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} 
+1
Mar 30 '15 at 17:08
source share

If performance doesn't really matter, you can use silly hack like:

 from collection import namedtuple Point = namedtuple('Point', 'xy z') mutable_z = Point(1,2,[3]) 
0
Oct 30 '16 at 20:04
source share

My reading of the topic may be superficial, but don't the main data classes provide this now?

0
Jan 24 '19 at 19:25
source share



All Articles