1 __all__
= ['deque', 'defaultdict', 'namedtuple']
2 # For bootstrapping reasons, the collection ABCs are defined in _abcoll.py.
3 # They should however be considered an integral part of collections.py.
6 __all__
+= _abcoll
.__all
__
8 from _collections
import deque
, defaultdict
9 from operator
import itemgetter
as _itemgetter
10 from keyword
import iskeyword
as _iskeyword
13 def namedtuple(typename
, field_names
, verbose
=False):
14 """Returns a new subclass of tuple with named fields.
16 >>> Point = namedtuple('Point', 'x y')
17 >>> Point.__doc__ # docstring for the new class
19 >>> p = Point(11, y=22) # instantiate with positional args or keywords
20 >>> p[0] + p[1] # indexable like a plain tuple
22 >>> x, y = p # unpack like a regular tuple
25 >>> p.x + p.y # fields also accessable by name
27 >>> d = p._asdict() # convert to a dictionary
30 >>> Point(**d) # convert from a dictionary
32 >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields
37 # Parse and validate the field names. Validation serves two purposes,
38 # generating informative error messages and preventing template injection attacks.
39 if isinstance(field_names
, basestring
):
40 field_names
= field_names
.replace(',', ' ').split() # names separated by whitespace and/or commas
41 field_names
= tuple(field_names
)
42 for name
in (typename
,) + field_names
:
43 if not all(c
.isalnum() or c
=='_' for c
in name
):
44 raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name
)
46 raise ValueError('Type names and field names cannot be a keyword: %r' % name
)
48 raise ValueError('Type names and field names cannot start with a number: %r' % name
)
50 for name
in field_names
:
51 if name
.startswith('_'):
52 raise ValueError('Field names cannot start with an underscore: %r' % name
)
53 if name
in seen_names
:
54 raise ValueError('Encountered duplicate field name: %r' % name
)
57 # Create and fill-in the class template
58 numfields
= len(field_names
)
59 argtxt
= repr(field_names
).replace("'", "")[1:-1] # tuple repr without parens or quotes
60 reprtxt
= ', '.join('%s=%%r' % name
for name
in field_names
)
61 dicttxt
= ', '.join('%r: t[%d]' % (name
, pos
) for pos
, name
in enumerate(field_names
))
62 template
= '''class %(typename)s(tuple):
63 '%(typename)s(%(argtxt)s)' \n
65 _fields = %(field_names)r \n
66 def __new__(cls, %(argtxt)s):
67 return tuple.__new__(cls, (%(argtxt)s)) \n
69 def _make(cls, iterable, new=tuple.__new__, len=len):
70 'Make a new %(typename)s object from a sequence or iterable'
71 result = new(cls, iterable)
72 if len(result) != %(numfields)d:
73 raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result))
76 return '%(typename)s(%(reprtxt)s)' %% self \n
78 'Return a new dict which maps field names to their values'
79 return {%(dicttxt)s} \n
80 def _replace(self, **kwds):
81 'Return a new %(typename)s object replacing specified fields with new values'
82 result = self._make(map(kwds.pop, %(field_names)r, self))
84 raise ValueError('Got unexpected field names: %%r' %% kwds.keys())
85 return result \n\n''' % locals()
86 for i
, name
in enumerate(field_names
):
87 template
+= ' %s = property(itemgetter(%d))\n' % (name
, i
)
91 # Execute the template string in a temporary namespace
92 namespace
= dict(itemgetter
=_itemgetter
)
94 exec template
in namespace
95 except SyntaxError, e
:
96 raise SyntaxError(e
.message
+ ':\n' + template
)
97 result
= namespace
[typename
]
99 # For pickling to work, the __module__ variable needs to be set to the frame
100 # where the named tuple is created. Bypass this step in enviroments where
101 # sys._getframe is not defined (Jython for example).
102 if hasattr(_sys
, '_getframe'):
103 result
.__module
__ = _sys
._getframe
(1).f_globals
['__name__']
112 if __name__
== '__main__':
113 # verify that instances can be pickled
114 from cPickle
import loads
, dumps
115 Point
= namedtuple('Point', 'x, y', True)
116 p
= Point(x
=10, y
=20)
117 assert p
== loads(dumps(p
))
119 # test and demonstrate ability to override methods
120 class Point(namedtuple('Point', 'x y')):
124 return (self
.x
** 2 + self
.y
** 2) ** 0.5
126 return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self
.x
, self
.y
, self
.hypot
)
128 for p
in Point(3, 4), Point(14, 5/7.):
131 class Point(namedtuple('Point', 'x y')):
132 'Point class with optimized _make() and _replace() without error-checking'
134 _make
= classmethod(tuple.__new
__)
135 def _replace(self
, _map
=map, **kwds
):
136 return self
._make
(_map(kwds
.get
, ('x', 'y'), self
))
138 print Point(11, 22)._replace
(x
=100)
140 Point3D
= namedtuple('Point3D', Point
._fields
+ ('z',))
141 print Point3D
.__doc
__
144 TestResults
= namedtuple('TestResults', 'failed attempted')
145 print TestResults(*doctest
.testmod())