mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-07 10:22:27 +08:00
112 lines
3.2 KiB
Python
112 lines
3.2 KiB
Python
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
|
|
inheritence 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)
|