Created
July 30, 2014 14:01
-
-
Save akaptur/d3ae324648926be77589 to your computer and use it in GitHub Desktop.
Named tuples from Python's Lib/collections.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
################################################################################ | |
### namedtuple | |
################################################################################ | |
_class_template = '''\ | |
class {typename}(tuple): | |
'{typename}({arg_list})' | |
__slots__ = () | |
_fields = {field_names!r} | |
def __new__(_cls, {arg_list}): | |
'Create new instance of {typename}({arg_list})' | |
return _tuple.__new__(_cls, ({arg_list})) | |
@classmethod | |
def _make(cls, iterable, new=tuple.__new__, len=len): | |
'Make a new {typename} object from a sequence or iterable' | |
result = new(cls, iterable) | |
if len(result) != {num_fields:d}: | |
raise TypeError('Expected {num_fields:d} arguments, got %d' % len(result)) | |
return result | |
def __repr__(self): | |
'Return a nicely formatted representation string' | |
return '{typename}({repr_fmt})' % self | |
def _asdict(self): | |
'Return a new OrderedDict which maps field names to their values' | |
return OrderedDict(zip(self._fields, self)) | |
def _replace(_self, **kwds): | |
'Return a new {typename} object replacing specified fields with new values' | |
result = _self._make(map(kwds.pop, {field_names!r}, _self)) | |
if kwds: | |
raise ValueError('Got unexpected field names: %r' % kwds.keys()) | |
return result | |
def __getnewargs__(self): | |
'Return self as a plain tuple. Used by copy and pickle.' | |
return tuple(self) | |
__dict__ = _property(_asdict) | |
def __getstate__(self): | |
'Exclude the OrderedDict from pickling' | |
pass | |
{field_defs} | |
''' | |
_repr_template = '{name}=%r' | |
_field_template = '''\ | |
{name} = _property(_itemgetter({index:d}), doc='Alias for field number {index:d}') | |
''' | |
def namedtuple(typename, field_names, verbose=False, rename=False): | |
"""Returns a new subclass of tuple with named fields. | |
>>> Point = namedtuple('Point', ['x', 'y']) | |
>>> Point.__doc__ # docstring for the new class | |
'Point(x, y)' | |
>>> p = Point(11, y=22) # instantiate with positional args or keywords | |
>>> p[0] + p[1] # indexable like a plain tuple | |
33 | |
>>> x, y = p # unpack like a regular tuple | |
>>> x, y | |
(11, 22) | |
>>> p.x + p.y # fields also accessable by name | |
33 | |
>>> d = p._asdict() # convert to a dictionary | |
>>> d['x'] | |
11 | |
>>> Point(**d) # convert from a dictionary | |
Point(x=11, y=22) | |
>>> p._replace(x=100) # _replace() is like str.replace() but targets named fields | |
Point(x=100, y=22) | |
""" | |
# Validate the field names. At the user's option, either generate an error | |
# message or automatically replace the field name with a valid name. | |
if isinstance(field_names, basestring): | |
field_names = field_names.replace(',', ' ').split() | |
field_names = map(str, field_names) | |
typename = str(typename) | |
if rename: | |
seen = set() | |
for index, name in enumerate(field_names): | |
if (not all(c.isalnum() or c=='_' for c in name) | |
or _iskeyword(name) | |
or not name | |
or name[0].isdigit() | |
or name.startswith('_') | |
or name in seen): | |
field_names[index] = '_%d' % index | |
seen.add(name) | |
for name in [typename] + field_names: | |
if type(name) != str: | |
raise TypeError('Type names and field names must be strings') | |
if not all(c.isalnum() or c=='_' for c in name): | |
raise ValueError('Type names and field names can only contain ' | |
'alphanumeric characters and underscores: %r' % name) | |
if _iskeyword(name): | |
raise ValueError('Type names and field names cannot be a ' | |
'keyword: %r' % name) | |
if name[0].isdigit(): | |
raise ValueError('Type names and field names cannot start with ' | |
'a number: %r' % name) | |
seen = set() | |
for name in field_names: | |
if name.startswith('_') and not rename: | |
raise ValueError('Field names cannot start with an underscore: ' | |
'%r' % name) | |
if name in seen: | |
raise ValueError('Encountered duplicate field name: %r' % name) | |
seen.add(name) | |
# Fill-in the class template | |
class_definition = _class_template.format( | |
typename = typename, | |
field_names = tuple(field_names), | |
num_fields = len(field_names), | |
arg_list = repr(tuple(field_names)).replace("'", "")[1:-1], | |
repr_fmt = ', '.join(_repr_template.format(name=name) | |
for name in field_names), | |
field_defs = '\n'.join(_field_template.format(index=index, name=name) | |
for index, name in enumerate(field_names)) | |
) | |
if verbose: | |
print class_definition | |
# Execute the template string in a temporary namespace and support | |
# tracing utilities by setting a value for frame.f_globals['__name__'] | |
namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename, | |
OrderedDict=OrderedDict, _property=property, _tuple=tuple) | |
try: | |
exec class_definition in namespace | |
except SyntaxError as e: | |
raise SyntaxError(e.message + ':\n' + class_definition) | |
result = namespace[typename] | |
# For pickling to work, the __module__ variable needs to be set to the frame | |
# where the named tuple is created. Bypass this step in environments where | |
# sys._getframe is not defined (Jython for example) or sys._getframe is not | |
# defined for arguments greater than 0 (IronPython). | |
try: | |
result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__') | |
except (AttributeError, ValueError): | |
pass | |
return result |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment