123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- # http://code.activestate.com/recipes/577629-namedtupleabc-abstract-base-class-mix-in-for-named/
- # Copyright (c) 2011 Jan Kaliszewski (zuo). Available under the MIT License.
- #
- # SPDX-License-Identifier: MIT
- #
- """
- namedtuple_with_abc.py:
- * named tuple mix-in + ABC (abstract base class) recipe,
- * works under Python 2.6, 2.7 as well as 3.x.
- Import this module to patch collections.namedtuple() factory function
- -- enriching it with the 'abc' attribute (an abstract base class + mix-in
- for named tuples) and decorating it with a wrapper that registers each
- newly created named tuple as a subclass of namedtuple.abc.
- How to import:
- import collections, namedtuple_with_abc
- or:
- import namedtuple_with_abc
- from collections import namedtuple
- # ^ in this variant you must import namedtuple function
- # *after* importing namedtuple_with_abc module
- or simply:
- from namedtuple_with_abc import namedtuple
- Simple usage example:
- class Credentials(namedtuple.abc):
- _fields = 'username password'
- def __str__(self):
- return ('{0.__class__.__name__}'
- '(username={0.username}, password=...)'.format(self))
- print(Credentials("alice", "Alice's password"))
- For more advanced examples -- see below the "if __name__ == '__main__':".
- """
- import collections
- from abc import ABCMeta, abstractproperty
- from functools import wraps
- from sys import version_info
- __all__ = ('namedtuple',)
- _namedtuple = collections.namedtuple
- class _NamedTupleABCMeta(ABCMeta):
- '''The metaclass for the abstract base class + mix-in for named tuples.'''
- def __new__(mcls, name, bases, namespace):
- fields = namespace.get('_fields')
- for base in bases:
- if fields is not None:
- break
- fields = getattr(base, '_fields', None)
- if not isinstance(fields, abstractproperty):
- basetuple = _namedtuple(name, fields)
- bases = (basetuple,) + bases
- namespace.pop('_fields', None)
- namespace.setdefault('__doc__', basetuple.__doc__)
- namespace.setdefault('__slots__', ())
- return ABCMeta.__new__(mcls, name, bases, namespace)
- class _NamedTupleABC(metaclass=_NamedTupleABCMeta):
- '''The abstract base class + mix-in for named tuples.'''
- _fields = abstractproperty()
- _namedtuple.abc = _NamedTupleABC
- #_NamedTupleABC.register(type(version_info)) # (and similar, in the future...)
- @wraps(_namedtuple)
- def namedtuple(*args, **kwargs):
- '''Named tuple factory with namedtuple.abc subclass registration.'''
- cls = _namedtuple(*args, **kwargs)
- _NamedTupleABC.register(cls)
- return cls
- collections.namedtuple = namedtuple
- if __name__ == '__main__':
- '''Examples and explanations'''
- # Simple usage
- class MyRecord(namedtuple.abc):
- _fields = 'x y z' # such form will be transformed into ('x', 'y', 'z')
- def _my_custom_method(self):
- return list(self._asdict().items())
- # (the '_fields' attribute belongs to the named tuple public API anyway)
- rec = MyRecord(1, 2, 3)
- print(rec)
- print(rec._my_custom_method())
- print(rec._replace(y=222))
- print(rec._replace(y=222)._my_custom_method())
- # Custom abstract classes...
- class MyAbstractRecord(namedtuple.abc):
- def _my_custom_method(self):
- return list(self._asdict().items())
- try:
- MyAbstractRecord() # (abstract classes cannot be instantiated)
- except TypeError as exc:
- print(exc)
- class AnotherAbstractRecord(MyAbstractRecord):
- def __str__(self):
- return '<<<{0}>>>'.format(super(AnotherAbstractRecord,
- self).__str__())
- # ...and their non-abstract subclasses
- class MyRecord2(MyAbstractRecord):
- _fields = 'a, b'
- class MyRecord3(AnotherAbstractRecord):
- _fields = 'p', 'q', 'r'
- rec2 = MyRecord2('foo', 'bar')
- print(rec2)
- print(rec2._my_custom_method())
- print(rec2._replace(b=222))
- print(rec2._replace(b=222)._my_custom_method())
- rec3 = MyRecord3('foo', 'bar', 'baz')
- print(rec3)
- print(rec3._my_custom_method())
- print(rec3._replace(q=222))
- print(rec3._replace(q=222)._my_custom_method())
- # You can also subclass non-abstract ones...
- class MyRecord33(MyRecord3):
- def __str__(self):
- return '< {0!r}, ..., {0!r} >'.format(self.p, self.r)
- rec33 = MyRecord33('foo', 'bar', 'baz')
- print(rec33)
- print(rec33._my_custom_method())
- print(rec33._replace(q=222))
- print(rec33._replace(q=222)._my_custom_method())
- # ...and even override the magic '_fields' attribute again
- class MyRecord345(MyRecord3):
- _fields = 'e f g h i j k'
- rec345 = MyRecord345(1, 2, 3, 4, 3, 2, 1)
- print(rec345)
- print(rec345._my_custom_method())
- print(rec345._replace(f=222))
- print(rec345._replace(f=222)._my_custom_method())
- # Mixing-in some other classes is also possible:
- class MyMixIn(object):
- def method(self):
- return "MyMixIn.method() called"
- def _my_custom_method(self):
- return "MyMixIn._my_custom_method() called"
- def count(self, item):
- return "MyMixIn.count({0}) called".format(item)
- def _asdict(self): # (cannot override a namedtuple method, see below)
- return "MyMixIn._asdict() called"
- class MyRecord4(MyRecord33, MyMixIn): # mix-in on the right
- _fields = 'j k l x'
- class MyRecord5(MyMixIn, MyRecord33): # mix-in on the left
- _fields = 'j k l x y'
- rec4 = MyRecord4(1, 2, 3, 2)
- print(rec4)
- print(rec4.method())
- print(rec4._my_custom_method()) # MyRecord33's
- print(rec4.count(2)) # tuple's
- print(rec4._replace(k=222))
- print(rec4._replace(k=222).method())
- print(rec4._replace(k=222)._my_custom_method()) # MyRecord33's
- print(rec4._replace(k=222).count(8)) # tuple's
- rec5 = MyRecord5(1, 2, 3, 2, 1)
- print(rec5)
- print(rec5.method())
- print(rec5._my_custom_method()) # MyMixIn's
- print(rec5.count(2)) # MyMixIn's
- print(rec5._replace(k=222))
- print(rec5._replace(k=222).method())
- print(rec5._replace(k=222)._my_custom_method()) # MyMixIn's
- print(rec5._replace(k=222).count(2)) # MyMixIn's
- # Note that behavior: the standard namedtuple methods cannot be
- # overridden by a foreign mix-in -- even if the mix-in is declared
- # as the leftmost base class (but, obviously, you can override them
- # in the defined class or its subclasses):
- print(rec4._asdict()) # (returns a dict, not "MyMixIn._asdict() called")
- print(rec5._asdict()) # (returns a dict, not "MyMixIn._asdict() called")
- class MyRecord6(MyRecord33):
- _fields = 'j k l x y z'
- def _asdict(self):
- return "MyRecord6._asdict() called"
- rec6 = MyRecord6(1, 2, 3, 1, 2, 3)
- print(rec6._asdict()) # (this returns "MyRecord6._asdict() called")
- # All that record classes are real subclasses of namedtuple.abc:
- assert issubclass(MyRecord, namedtuple.abc)
- assert issubclass(MyAbstractRecord, namedtuple.abc)
- assert issubclass(AnotherAbstractRecord, namedtuple.abc)
- assert issubclass(MyRecord2, namedtuple.abc)
- assert issubclass(MyRecord3, namedtuple.abc)
- assert issubclass(MyRecord33, namedtuple.abc)
- assert issubclass(MyRecord345, namedtuple.abc)
- assert issubclass(MyRecord4, namedtuple.abc)
- assert issubclass(MyRecord5, namedtuple.abc)
- assert issubclass(MyRecord6, namedtuple.abc)
- # ...but abstract ones are not subclasses of tuple
- # (and this is what you probably want):
- assert not issubclass(MyAbstractRecord, tuple)
- assert not issubclass(AnotherAbstractRecord, tuple)
- assert issubclass(MyRecord, tuple)
- assert issubclass(MyRecord2, tuple)
- assert issubclass(MyRecord3, tuple)
- assert issubclass(MyRecord33, tuple)
- assert issubclass(MyRecord345, tuple)
- assert issubclass(MyRecord4, tuple)
- assert issubclass(MyRecord5, tuple)
- assert issubclass(MyRecord6, tuple)
- # Named tuple classes created with namedtuple() factory function
- # (in the "traditional" way) are registered as "virtual" subclasses
- # of namedtuple.abc:
- MyTuple = namedtuple('MyTuple', 'a b c')
- mt = MyTuple(1, 2, 3)
- assert issubclass(MyTuple, namedtuple.abc)
- assert isinstance(mt, namedtuple.abc)
|