Guest User

Untitled

a guest
Jul 19th, 2018
114
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.44 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # Copyright (c) 2011 Jan Kaliszewski (zuo). Available under the MIT License.
  3.  
  4. """
  5. namedtuple_with_abc.py:
  6. * named tuple mix-in + ABC (abstract base class) recipe,
  7. * works under Python 2.6, 2.7 as well as 3.x.
  8.  
  9. Import this module to patch collections.namedtuple() factory function
  10. -- enriching it with the 'abc' attribute (an abstract base class + mix-in
  11. for named tuples) and decorating it with a wrapper that registers each
  12. newly created named tuple as a subclass of namedtuple.abc.
  13.  
  14. How to import:
  15. import collections, namedtuple_with_abc
  16. or:
  17. import namedtuple_with_abc
  18. from collections import namedtuple
  19. # ^ in this variant you must import namedtuple function
  20. # *after* importing namedtuple_with_abc module
  21. or simply:
  22. from namedtuple_with_abc import namedtuple
  23.  
  24. Simple usage example:
  25. class Credentials(namedtuple.abc):
  26. _fields = 'username password'
  27. def __str__(self):
  28. return ('{0.__class__.__name__}'
  29. '(username={0.username}, password=...)'.format(self))
  30. print(Credentials("alice", "Alice's password"))
  31.  
  32. For more advanced examples -- see below the "if __name__ == '__main__':".
  33. """
  34.  
  35. import collections
  36. from abc import ABCMeta, abstractproperty
  37. from functools import wraps
  38. from sys import version_info
  39.  
  40. __all__ = ('namedtuple',)
  41. _namedtuple = collections.namedtuple
  42.  
  43.  
  44. class _NamedTupleABCMeta(ABCMeta):
  45. '''The metaclass for the abstract base class + mix-in for named tuples.'''
  46. def __new__(mcls, name, bases, namespace):
  47. fields = namespace.get('_fields')
  48. for base in bases:
  49. if fields is not None:
  50. break
  51. fields = getattr(base, '_fields', None)
  52. if not isinstance(fields, abstractproperty):
  53. basetuple = _namedtuple(name, fields)
  54. bases = (basetuple,) + bases
  55. namespace.pop('_fields', None)
  56. namespace.setdefault('__doc__', basetuple.__doc__)
  57. namespace.setdefault('__slots__', ())
  58. return ABCMeta.__new__(mcls, name, bases, namespace)
  59.  
  60.  
  61. exec(
  62. # Python 2.x metaclass declaration syntax
  63. """class _NamedTupleABC(object):
  64. '''The abstract base class + mix-in for named tuples.'''
  65. __metaclass__ = _NamedTupleABCMeta
  66. _fields = abstractproperty()""" if version_info[0] < 3 else
  67. # Python 3.x metaclass declaration syntax
  68. """class _NamedTupleABC(metaclass=_NamedTupleABCMeta):
  69. '''The abstract base class + mix-in for named tuples.'''
  70. _fields = abstractproperty()"""
  71. )
  72.  
  73.  
  74. _namedtuple.abc = _NamedTupleABC
  75. #_NamedTupleABC.register(type(version_info)) # (and similar, in the future...)
  76.  
  77. @wraps(_namedtuple)
  78. def namedtuple(*args, **kwargs):
  79. '''Named tuple factory with namedtuple.abc subclass registration.'''
  80. cls = _namedtuple(*args, **kwargs)
  81. _NamedTupleABC.register(cls)
  82. return cls
  83.  
  84. collections.namedtuple = namedtuple
  85.  
  86.  
  87.  
  88.  
  89. if __name__ == '__main__':
  90.  
  91. '''Examples and explanations'''
  92.  
  93. # Simple usage
  94.  
  95. class MyRecord(namedtuple.abc):
  96. _fields = 'x y z' # such form will be transformed into ('x', 'y', 'z')
  97. def _my_custom_method(self):
  98. return list(self._asdict().items())
  99. # (the '_fields' attribute belongs to the named tuple public API anyway)
  100.  
  101. rec = MyRecord(1, 2, 3)
  102. print(rec)
  103. print(rec._my_custom_method())
  104. print(rec._replace(y=222))
  105. print(rec._replace(y=222)._my_custom_method())
  106.  
  107. # Custom abstract classes...
  108.  
  109. class MyAbstractRecord(namedtuple.abc):
  110. def _my_custom_method(self):
  111. return list(self._asdict().items())
  112.  
  113. try:
  114. MyAbstractRecord() # (abstract classes cannot be instantiated)
  115. except TypeError as exc:
  116. print(exc)
  117.  
  118. class AnotherAbstractRecord(MyAbstractRecord):
  119. def __str__(self):
  120. return '<<<{0}>>>'.format(super(AnotherAbstractRecord,
  121. self).__str__())
  122.  
  123. # ...and their non-abstract subclasses
  124.  
  125. class MyRecord2(MyAbstractRecord):
  126. _fields = 'a, b'
  127.  
  128. class MyRecord3(AnotherAbstractRecord):
  129. _fields = 'p', 'q', 'r'
  130.  
  131. rec2 = MyRecord2('foo', 'bar')
  132. print(rec2)
  133. print(rec2._my_custom_method())
  134. print(rec2._replace(b=222))
  135. print(rec2._replace(b=222)._my_custom_method())
  136.  
  137. rec3 = MyRecord3('foo', 'bar', 'baz')
  138. print(rec3)
  139. print(rec3._my_custom_method())
  140. print(rec3._replace(q=222))
  141. print(rec3._replace(q=222)._my_custom_method())
  142.  
  143. # You can also subclass non-abstract ones...
  144.  
  145. class MyRecord33(MyRecord3):
  146. def __str__(self):
  147. return '< {0!r}, ..., {0!r} >'.format(self.p, self.r)
  148.  
  149. rec33 = MyRecord33('foo', 'bar', 'baz')
  150. print(rec33)
  151. print(rec33._my_custom_method())
  152. print(rec33._replace(q=222))
  153. print(rec33._replace(q=222)._my_custom_method())
  154.  
  155. # ...and even override the magic '_fields' attribute again
  156.  
  157. class MyRecord345(MyRecord3):
  158. _fields = 'e f g h i j k'
  159.  
  160. rec345 = MyRecord345(1, 2, 3, 4, 3, 2, 1)
  161. print(rec345)
  162. print(rec345._my_custom_method())
  163. print(rec345._replace(f=222))
  164. print(rec345._replace(f=222)._my_custom_method())
  165.  
  166. # Mixing-in some other classes is also possible:
  167.  
  168. class MyMixIn(object):
  169. def method(self):
  170. return "MyMixIn.method() called"
  171. def _my_custom_method(self):
  172. return "MyMixIn._my_custom_method() called"
  173. def count(self, item):
  174. return "MyMixIn.count({0}) called".format(item)
  175. def _asdict(self): # (cannot override a namedtuple method, see below)
  176. return "MyMixIn._asdict() called"
  177.  
  178. class MyRecord4(MyRecord33, MyMixIn): # mix-in on the right
  179. _fields = 'j k l x'
  180.  
  181. class MyRecord5(MyMixIn, MyRecord33): # mix-in on the left
  182. _fields = 'j k l x y'
  183.  
  184. rec4 = MyRecord4(1, 2, 3, 2)
  185. print(rec4)
  186. print(rec4.method())
  187. print(rec4._my_custom_method()) # MyRecord33's
  188. print(rec4.count(2)) # tuple's
  189. print(rec4._replace(k=222))
  190. print(rec4._replace(k=222).method())
  191. print(rec4._replace(k=222)._my_custom_method()) # MyRecord33's
  192. print(rec4._replace(k=222).count(8)) # tuple's
  193.  
  194. rec5 = MyRecord5(1, 2, 3, 2, 1)
  195. print(rec5)
  196. print(rec5.method())
  197. print(rec5._my_custom_method()) # MyMixIn's
  198. print(rec5.count(2)) # MyMixIn's
  199. print(rec5._replace(k=222))
  200. print(rec5._replace(k=222).method())
  201. print(rec5._replace(k=222)._my_custom_method()) # MyMixIn's
  202. print(rec5._replace(k=222).count(2)) # MyMixIn's
  203.  
  204. # None that behavior: the standard namedtuple methods cannot be
  205. # overriden by a foreign mix-in -- even if the mix-in is declared
  206. # as the leftmost base class (but, obviously, you can override them
  207. # in the defined class or its subclasses):
  208.  
  209. print(rec4._asdict()) # (returns a dict, not "MyMixIn._asdict() called")
  210. print(rec5._asdict()) # (returns a dict, not "MyMixIn._asdict() called")
  211.  
  212. class MyRecord6(MyRecord33):
  213. _fields = 'j k l x y z'
  214. def _asdict(self):
  215. return "MyRecord6._asdict() called"
  216. rec6 = MyRecord6(1, 2, 3, 1, 2, 3)
  217. print(rec6._asdict()) # (this returns "MyRecord6._asdict() called")
  218.  
  219. # All that record classes are real subclasses of namedtuple.abc:
  220.  
  221. assert issubclass(MyRecord, namedtuple.abc)
  222. assert issubclass(MyAbstractRecord, namedtuple.abc)
  223. assert issubclass(AnotherAbstractRecord, namedtuple.abc)
  224. assert issubclass(MyRecord2, namedtuple.abc)
  225. assert issubclass(MyRecord3, namedtuple.abc)
  226. assert issubclass(MyRecord33, namedtuple.abc)
  227. assert issubclass(MyRecord345, namedtuple.abc)
  228. assert issubclass(MyRecord4, namedtuple.abc)
  229. assert issubclass(MyRecord5, namedtuple.abc)
  230. assert issubclass(MyRecord6, namedtuple.abc)
  231.  
  232. # ...but abstract ones are not subclasses of tuple
  233. # (and this is what you probably want):
  234.  
  235. assert not issubclass(MyAbstractRecord, tuple)
  236. assert not issubclass(AnotherAbstractRecord, tuple)
  237.  
  238. assert issubclass(MyRecord, tuple)
  239. assert issubclass(MyRecord2, tuple)
  240. assert issubclass(MyRecord3, tuple)
  241. assert issubclass(MyRecord33, tuple)
  242. assert issubclass(MyRecord345, tuple)
  243. assert issubclass(MyRecord4, tuple)
  244. assert issubclass(MyRecord5, tuple)
  245. assert issubclass(MyRecord6, tuple)
  246.  
  247. # Named tuple classes created with namedtuple() factory function
  248. # (in the "traditional" way) are registered as "virtual" subclasses
  249. # of namedtuple.abc:
  250.  
  251. MyTuple = namedtuple('MyTuple', 'a b c')
  252. mt = MyTuple(1, 2, 3)
  253. assert issubclass(MyTuple, namedtuple.abc)
  254. assert isinstance(mt, namedtuple.abc)
Add Comment
Please, Sign In to add comment