-
Notifications
You must be signed in to change notification settings - Fork 4.7k
/
metautils.py
111 lines (82 loc) · 3.24 KB
/
metautils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
from operator import attrgetter
import six
def compose_types(a, *cs):
"""Compose multiple classes together.
Parameters
----------
*mcls : tuple[type]
The classes that you would like to compose
Returns
-------
cls : type
A type that subclasses all of the types in ``mcls``.
Notes
-----
A common use case for this is to build composed metaclasses, for example,
imagine you have some simple metaclass ``M`` and some instance of ``M``
named ``C`` like so:
.. code-block:: python
>>> class M(type):
... def __new__(mcls, name, bases, dict_):
... dict_['ayy'] = 'lmao'
... return super(M, mcls).__new__(mcls, name, bases, dict_)
>>> from six import with_metaclass
>>> class C(with_metaclass(M, object)):
... pass
We now want to create a sublclass of ``C`` that is also an abstract class.
We can use ``compose_types`` to create a new metaclass that is a subclass
of ``M`` and ``ABCMeta``. This is needed because a subclass of a class
with a metaclass must have a metaclass which is a subclass of the metaclass
of the superclass.
.. code-block:: python
>>> from abc import ABCMeta, abstractmethod
>>> class D(with_metaclass(compose_types(M, ABCMeta), C)):
... @abstractmethod
... def f(self):
... raise NotImplementedError('f')
We can see that this class has both metaclasses applied to it:
.. code-block:: python
>>> D.ayy
'lmao'
>>> D()
Traceback (most recent call last):
...
TypeError: Can't instantiate abstract class D with abstract methods f
An important note here is that ``M`` did not use ``type.__new__`` and
instead used ``super()``. This is to support cooperative multiple
inheritance which is needed for ``compose_types`` to work as intended.
After we have composed these types ``M.__new__``\'s super will actually
go to ``ABCMeta.__new__`` and not ``type.__new__``.
Always using ``super()`` to dispatch to your superclass is best practices
anyways so most classes should compose without much special considerations.
"""
if not cs:
# if there are no types to compose then just return the single type
return a
mcls = (a,) + cs
return type(
'compose_types(%s)' % ', '.join(map(attrgetter('__name__'), mcls)),
mcls,
{},
)
def with_metaclasses(metaclasses, *bases):
"""Make a class inheriting from ``bases`` whose metaclass inherits from
all of ``metaclasses``.
Like :func:`six.with_metaclass`, but allows multiple metaclasses.
Parameters
----------
metaclasses : iterable[type]
A tuple of types to use as metaclasses.
*bases : tuple[type]
A tuple of types to use as bases.
Returns
-------
base : type
A subtype of ``bases`` whose metaclass is a subtype of ``metaclasses``.
Notes
-----
The metaclasses must be written to support cooperative multiple
inheritance. This means that they must delegate all calls to ``super()``
instead of inlining their super class by name.
"""
return six.with_metaclass(compose_types(*metaclasses), *bases)