Advertisement
Guest User

Untitled

a guest
Oct 17th, 2011
186
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.11 KB | None | 0 0
  1. "collections.namedtuple implementation without using exec."
  2. from collections import OrderedDict
  3. from keyword import iskeyword
  4. from operator import itemgetter
  5. import itertools
  6. import sys
  7.  
  8. __all__ = ['NamedTuple', 'namedtuple']
  9.  
  10.  
  11. class NamedTuple(type):
  12. """Metaclass for a new subclass of tuple with named fields.
  13.  
  14. >>> class Point(metaclass=NamedTuple):
  15. ... _fields = ['x', 'y']
  16. ...
  17. >>> Point.__doc__ # docstring for the new class
  18. 'Point(x, y)'
  19. >>> p = Point(11, y=22) # instantiate with positional args or keywords
  20. >>> p[0] + p[1] # indexable like a plain tuple
  21. 33
  22. >>> x, y = p # unpack like a regular tuple
  23. >>> x, y
  24. (11, 22)
  25. >>> p.x + p.y # fields also accessable by name
  26. 33
  27. >>> d = p._asdict() # convert to a dictionary
  28. >>> d['x']
  29. 11
  30. >>> Point(**d) # convert from a dictionary
  31. Point(x=11, y=22)
  32. >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields
  33. Point(x=100, y=22)
  34.  
  35. """
  36.  
  37. def __new__(meta, classname, bases, classdict):
  38. if '_fields' not in classdict:
  39. raise ValueError("NamedTuple must have _fields attribute.")
  40. if tuple not in bases:
  41. bases = tuple(itertools.chain(itertools.repeat(tuple, 1), bases))
  42. for pos, name in enumerate(classdict['_fields']):
  43. classdict[name] = property(itemgetter(pos),
  44. doc='Alias for field number {0:d}'.format(pos))
  45. classdict.update(meta.NAMESPACE)
  46. classdict['__doc__'] = '{0:s}({1:s})'.format(classname,
  47. repr(classdict['_fields']).replace("'", "")[1:-1])
  48. cls = type.__new__(meta, classname, bases, classdict)
  49. cls._make = classmethod(cls._make)
  50. return cls
  51.  
  52. def _new(cls, *args, **kwargs):
  53. 'Create new instance of {0:s}({1:s})'.format(cls.__name__, cls.__doc__)
  54. expected = len(cls._fields)
  55. received = len(args) + len(kwargs)
  56. if received != expected:
  57. raise TypeError('__new__() takes exactly {0:d} arguments ({1:d} given)'.format(received, expected))
  58. values = itertools.chain(args,
  59. (kwargs[name] for name in cls._fields[len(args):]))
  60. return tuple.__new__(cls, values)
  61.  
  62. def _make(cls, iterable, new=tuple.__new__, len=len):
  63. 'Make a new {0:s} object from a sequence or iterable'.format(cls.__name__)
  64. result = new(cls, iterable)
  65. if len(result) != len(cls._fields):
  66. raise TypeError('Expected {0:d} arguments, got {1:d}'.format(
  67. len(cls._fields), len(result)))
  68. return result
  69.  
  70. def _repr(self):
  71. 'Return a nicely formatted representation string'
  72. keywords = ', '.join('{0:s}={1!r:s}'.format(self._fields[i], self[i])
  73. for i in itertools.islice(itertools.count(), len(self._fields)))
  74. classname = self.__class__.__name__
  75. return '{0:s}({1:s})'.format(classname, keywords)
  76.  
  77. def _asdict(self):
  78. 'Return a new OrderedDict which maps field names to their values'
  79. return OrderedDict(zip(self._fields, self))
  80.  
  81. def _replace(self, **kwargs):
  82. 'Return a new {0:s} object replacing specified fields with new values'.format(self.__class__.__name__)
  83. result = self._make(map(kwargs.pop, self._fields, self))
  84. if kwargs:
  85. raise ValueError('Got unexpected field names: {0:r}'.format(kwargs.keys()))
  86. return result
  87.  
  88. def _getnewargs(self):
  89. 'Return self as a plain tuple. Used by copy and pickle.'
  90. return tuple(self)
  91.  
  92. NAMESPACE = {
  93. '__slots__': (),
  94. '__new__': _new,
  95. '_make': _make,
  96. '__repr__': _repr,
  97. '_asdict': _asdict,
  98. '_replace': _replace,
  99. '__getnewargs__': _getnewargs,
  100. }
  101.  
  102.  
  103. def namedtuple(typename, field_names, rename=False):
  104. """Returns a new subclass of tuple with named fields.
  105.  
  106. >>> Point = namedtuple('Point', 'x y')
  107. >>> Point.__doc__ # docstring for the new class
  108. 'Point(x, y)'
  109. >>> p = Point(11, y=22) # instantiate with positional args or keywords
  110. >>> p[0] + p[1] # indexable like a plain tuple
  111. 33
  112. >>> x, y = p # unpack like a regular tuple
  113. >>> x, y
  114. (11, 22)
  115. >>> p.x + p.y # fields also accessable by name
  116. 33
  117. >>> d = p._asdict() # convert to a dictionary
  118. >>> d['x']
  119. 11
  120. >>> Point(**d) # convert from a dictionary
  121. Point(x=11, y=22)
  122. >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields
  123. Point(x=100, y=22)
  124.  
  125. """
  126. if hasattr(field_names, 'split'):
  127. field_names = field_names.replace(',', ' ').split()
  128. if rename:
  129. names = list(field_names)
  130. seen = set()
  131. for i, name in enumerate(names):
  132. if (not all(c.isalnum() or c == '_' for c in name) or iskeyword(name)
  133. or not name or name[0].isdigit() or name.startswith('_')
  134. or name in seen):
  135. names[i] = '_%d' % i
  136. seen.add(name)
  137. field_names = tuple(names)
  138. for name in (typename,) + field_names:
  139. if not all(c.isalnum() or c == '_' for c in name):
  140. raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: {0!r:s}'.format(name))
  141. if iskeyword(name):
  142. raise ValueError('Type names and field names cannot be a keyword: {0!r:s}'.format(name))
  143. if name[0].isdigit():
  144. raise ValueError('Type names and field names cannot start with a number: {0!r:s}'.format(name))
  145. seen_names = set()
  146. for name in field_names:
  147. if name.startswith('_') and not rename:
  148. raise ValueError('Field names cannot start with an underscore: {0!r:s}'.format(name))
  149. if name in seen_names:
  150. raise ValueError('Encountered duplicate field name: {0!r:s}'.format(name))
  151. seen_names.add(name)
  152.  
  153. result = NamedTuple.__new__(NamedTuple, typename, (tuple, object), {'_fields': tuple(field_names)})
  154.  
  155. # For pickling to work, the __module__ variable needs to be set to the frame
  156. # where the named tuple is created. Bypass this step in enviroments where
  157. # sys._getframe is not defined (Jython for example) or sys._getframe is not
  158. # defined for arguments greater than 0 (IronPython).
  159. try:
  160. result.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__')
  161. except (AttributeError, ValueError):
  162. pass
  163.  
  164. return result
  165.  
  166.  
  167. if __name__ == '__main__':
  168. class Test(object, metaclass=NamedTuple):
  169. _fields = ('alpha', 'beta', 'charlie')
  170.  
  171. t = Test(1, 2, charlie=3)
  172. print('doc:', t.__doc__)
  173. print('repr:', repr(t))
  174. print('asdict:', t._asdict())
  175. print('replace:', t._replace(alpha=4))
  176. print('make:', Test._make([1, 2, 3]))
  177.  
  178. Zoo = namedtuple('Zoo', 'lions tigers bears', rename=True)
  179. z = Zoo(8, 9, 10)
  180. print(z)
  181. print('lions:', z.lions)
  182. print('tigers:', z.tigers)
  183. print('bears:', z.bears)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement