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(map(str, 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())
86 def __getnewargs__(self):
87 return tuple(self) \n\n''' % locals()
88 for i
, name
in enumerate(field_names
):
89 template
+= ' %s = property(itemgetter(%d))\n' % (name
, i
)
93 # Execute the template string in a temporary namespace and
94 # support tracing utilities by setting a value for frame.f_globals['__name__']
95 namespace
= dict(itemgetter
=_itemgetter
, __name__
='namedtuple_%s' % typename
)
97 exec template
in namespace
98 except SyntaxError, e
:
99 raise SyntaxError(e
.message
+ ':\n' + template
)
100 result
= namespace
[typename
]
102 # For pickling to work, the __module__ variable needs to be set to the frame
103 # where the named tuple is created. Bypass this step in enviroments where
104 # sys._getframe is not defined (Jython for example).
105 if hasattr(_sys
, '_getframe'):
106 result
.__module
__ = _sys
._getframe
(1).f_globals
['__name__']
115 if __name__
== '__main__':
116 # verify that instances can be pickled
117 from cPickle
import loads
, dumps
118 Point
= namedtuple('Point', 'x, y', True)
119 p
= Point(x
=10, y
=20)
120 assert p
== loads(dumps(p
))
122 # test and demonstrate ability to override methods
123 class Point(namedtuple('Point', 'x y')):
127 return (self
.x
** 2 + self
.y
** 2) ** 0.5
129 return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self
.x
, self
.y
, self
.hypot
)
131 for p
in Point(3, 4), Point(14, 5/7.):
134 class Point(namedtuple('Point', 'x y')):
135 'Point class with optimized _make() and _replace() without error-checking'
137 _make
= classmethod(tuple.__new
__)
138 def _replace(self
, _map
=map, **kwds
):
139 return self
._make
(_map(kwds
.get
, ('x', 'y'), self
))
141 print Point(11, 22)._replace
(x
=100)
143 Point3D
= namedtuple('Point3D', Point
._fields
+ ('z',))
144 print Point3D
.__doc
__
147 TestResults
= namedtuple('TestResults', 'failed attempted')
148 print TestResults(*doctest
.testmod())