Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- "collections.namedtuple implementation without using exec."
- from collections import OrderedDict
- from keyword import iskeyword
- from operator import itemgetter
- import itertools
- import sys
- __all__ = ['NamedTuple', 'namedtuple']
- class NamedTuple(type):
- """Metaclass for a new subclass of tuple with named fields.
- >>> class Point(metaclass=NamedTuple):
- ... _fields = ['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)
- """
- def __new__(meta, classname, bases, classdict):
- if '_fields' not in classdict:
- raise ValueError("NamedTuple must have _fields attribute.")
- if tuple not in bases:
- bases = tuple(itertools.chain(itertools.repeat(tuple, 1), bases))
- for pos, name in enumerate(classdict['_fields']):
- classdict[name] = property(itemgetter(pos),
- doc='Alias for field number {0:d}'.format(pos))
- classdict.update(meta.NAMESPACE)
- classdict['__doc__'] = '{0:s}({1:s})'.format(classname,
- repr(classdict['_fields']).replace("'", "")[1:-1])
- cls = type.__new__(meta, classname, bases, classdict)
- cls._make = classmethod(cls._make)
- return cls
- def _new(cls, *args, **kwargs):
- 'Create new instance of {0:s}({1:s})'.format(cls.__name__, cls.__doc__)
- expected = len(cls._fields)
- received = len(args) + len(kwargs)
- if received != expected:
- raise TypeError('__new__() takes exactly {0:d} arguments ({1:d} given)'.format(received, expected))
- values = itertools.chain(args,
- (kwargs[name] for name in cls._fields[len(args):]))
- return tuple.__new__(cls, values)
- def _make(cls, iterable, new=tuple.__new__, len=len):
- 'Make a new {0:s} object from a sequence or iterable'.format(cls.__name__)
- result = new(cls, iterable)
- if len(result) != len(cls._fields):
- raise TypeError('Expected {0:d} arguments, got {1:d}'.format(
- len(cls._fields), len(result)))
- return result
- def _repr(self):
- 'Return a nicely formatted representation string'
- keywords = ', '.join('{0:s}={1!r:s}'.format(self._fields[i], self[i])
- for i in itertools.islice(itertools.count(), len(self._fields)))
- classname = self.__class__.__name__
- return '{0:s}({1:s})'.format(classname, keywords)
- def _asdict(self):
- 'Return a new OrderedDict which maps field names to their values'
- return OrderedDict(zip(self._fields, self))
- def _replace(self, **kwargs):
- 'Return a new {0:s} object replacing specified fields with new values'.format(self.__class__.__name__)
- result = self._make(map(kwargs.pop, self._fields, self))
- if kwargs:
- raise ValueError('Got unexpected field names: {0:r}'.format(kwargs.keys()))
- return result
- def _getnewargs(self):
- 'Return self as a plain tuple. Used by copy and pickle.'
- return tuple(self)
- NAMESPACE = {
- '__slots__': (),
- '__new__': _new,
- '_make': _make,
- '__repr__': _repr,
- '_asdict': _asdict,
- '_replace': _replace,
- '__getnewargs__': _getnewargs,
- }
- def namedtuple(typename, field_names, 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)
- """
- if hasattr(field_names, 'split'):
- field_names = field_names.replace(',', ' ').split()
- if rename:
- names = list(field_names)
- seen = set()
- for i, name in enumerate(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):
- names[i] = '_%d' % i
- seen.add(name)
- field_names = tuple(names)
- for name in (typename,) + field_names:
- 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: {0!r:s}'.format(name))
- if iskeyword(name):
- raise ValueError('Type names and field names cannot be a keyword: {0!r:s}'.format(name))
- if name[0].isdigit():
- raise ValueError('Type names and field names cannot start with a number: {0!r:s}'.format(name))
- seen_names = set()
- for name in field_names:
- if name.startswith('_') and not rename:
- raise ValueError('Field names cannot start with an underscore: {0!r:s}'.format(name))
- if name in seen_names:
- raise ValueError('Encountered duplicate field name: {0!r:s}'.format(name))
- seen_names.add(name)
- result = NamedTuple.__new__(NamedTuple, typename, (tuple, object), {'_fields': tuple(field_names)})
- # For pickling to work, the __module__ variable needs to be set to the frame
- # where the named tuple is created. Bypass this step in enviroments 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
- if __name__ == '__main__':
- class Test(object, metaclass=NamedTuple):
- _fields = ('alpha', 'beta', 'charlie')
- t = Test(1, 2, charlie=3)
- print('doc:', t.__doc__)
- print('repr:', repr(t))
- print('asdict:', t._asdict())
- print('replace:', t._replace(alpha=4))
- print('make:', Test._make([1, 2, 3]))
- Zoo = namedtuple('Zoo', 'lions tigers bears', rename=True)
- z = Zoo(8, 9, 10)
- print(z)
- print('lions:', z.lions)
- print('tigers:', z.tigers)
- print('bears:', z.bears)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement