This repository was archived by the owner on Mar 23, 2023. It is now read-only.
This repository was archived by the owner on Mar 23, 2023. It is now read-only.
namedtuple excluded from collections module #29
Open
Description
I read that you don't want to have things exec
ing. Here's a rough sketch of what a namedtuple might look like without exec
.
class NamedTuple(tuple):
'tuple with named attribute aliases for elements'
def __new__(cls, *args, **kwds):
'Create new instance of {typename}'
excess = set(kwds) - set(cls._fields)
if excess:
raise TypeError('Unexpected keyword arguments %r' % excess)
kwds = sorted(kwds.items(), key=lambda pair: cls._fields.index(pair[0]))
return tuple.__new__(cls, args + tuple(v for k, v in kwds))
@classmethod
def _make(cls, iterable, new=tuple.__new__, len=len):
'Make a new {typename} object from a sequence or iterable'
n = len(cls._fields)
result = new(cls, iterable)
if len(result) != n:
raise TypeError('Expected %d arguments, got %d' % (n, len(result)))
return result
def __repr__(self):
'Return a nicely formatted representation string'
names = self._fields
values = [getattr(self, name) for name in self._fields]
attrstring = ', '.join('%s=%r' % (k, v) for k, v in zip(names, values))
return '%s(%s)' % (type(self).__name__, attrstring)
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, _self._fields, _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
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 accessible 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)
if verbose:
raise NotImplementedError('verbose mode irrelevant to this implementation')
bases = (NamedTuple,)
field = lambda i: property(itemgetter(i), doc='Alias for field number %d' % i)
attributes = {name: field(i) for i, name in enumerate(field_names)}
attributes['__slots__'] = ()
attributes['_fields'] = tuple(field_names)
attributes['__doc__'] = '%s(%s)' % (typename, str(field_names).replace("'", '')[1:-1])
result = type(typename, bases, attributes)
return result
I copy-pasted from the CPython stdlib, deleted the exec
and fiddled until it seemed to work.
Metadata
Metadata
Assignees
Labels
No labels