Created
September 5, 2019 17:38
-
-
Save cscutcher/2e633b646154798a5590b2aecb113dd2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class DirtyTemplateMeta(type): | |
''' | |
Metaclass for :class:`DirtyTemplate` | |
''' | |
def __new__(cls, name, bases, dct, *, template_bases=()): | |
new_cls = super().__new__(cls, name, bases, dct) | |
new_cls.template_dict = dct | |
new_cls.template_bases = template_bases | |
return new_cls | |
class DirtyTemplate(metaclass=DirtyTemplateMeta): | |
''' | |
Define a DirtyTemplate. | |
This is a horrible hack job that allows (in theory anyway) to define | |
a 'Template' class which can be used a bit like templates in C++, in | |
particular to define a class once that can be rendered into multiple | |
defined classes each with different base classes. | |
This allows for some pretty wild stuff. | |
So we can define a template that bases ``dict``, implements a method, and | |
overrides another. | |
Warning | |
------- | |
As I say, this is a terrible hack. I was experimenting with ways to be able | |
to avoid repeating definitions but, due to some third-party dark magics, | |
was unable to rely on mixin's or composition to get the job done. | |
I'm not saying it's a good idea, or that it'll work, just a bit of | |
experimentation. | |
Examples | |
-------- | |
>>> class SomeTemplate(DirtyTemplate, template_bases=(dict,)): | |
... def some_method(self): | |
... return "hello world" | |
... def __getitem__(self, key): | |
... return "constant" | |
... | |
This shouldn't be instansiated directly. Instead it can be used to | |
render different versions of the same class with different base classes. | |
>>> @SomeTemplate.template | |
... class ImplA: | |
... def some_method(self): | |
... return "not hello" | |
... | |
However, each rendering always has the template as the 'top class' rather | |
than what you might expect. | |
>>> a = ImplA() | |
>>> a.some_method() | |
'hello world' | |
>>> a["thing"] | |
'constant' | |
It even has the base classes defined on the template; | |
>>> ImplA.__bases__ | |
(<class 'dirty_templates.ImplA'>, <class 'dict'>) | |
The behaviour from these base classes works as expected. | |
>>> a["thing"] = 4 | |
>>> a.values() | |
dict_values([4]) | |
In the next example we render a version of the template that inherits from | |
a subclass of one of the bases of the template; | |
>>> import collections | |
>>> @SomeTemplate.template | |
... class ImplB(collections.OrderedDict): | |
... pass | |
... | |
Here again the template behaviour is used; | |
>>> b = ImplB() | |
>>> b.some_method() | |
'hello world' | |
>>> b["thing"] | |
'constant' | |
Note though, because the wrapped ``ImplB`` class already bases ``dict`` | |
(via OrderedDict) it's not added to the list of ``__bases__`` like it was | |
in ``ImplA`` | |
>>> ImplB.__bases__ | |
(<class 'dirty_templates.ImplB'>,) | |
but again the behaviour of ``OrderedDict`` is accessible; | |
>>> b["thing"] = 4 | |
>>> b.move_to_end("thing") | |
Finally we can obviously define new methods in the rendered classes; | |
>>> @SomeTemplate.template | |
... class ImplC(collections.OrderedDict): | |
... def some_new_method(self): | |
... return "new hello" | |
... | |
... | |
>>> c = ImplC() | |
>>> c.some_method() | |
'hello world' | |
>>> c.some_new_method() | |
'new hello' | |
>>> c["thing"] | |
'constant' | |
''' | |
# This is set by metaclass | |
template_dict = None | |
template_bases = () | |
@classmethod | |
def template(cls, new_cls): | |
''' | |
Decorator to generate new templated classes. | |
''' | |
name = new_cls.__name__ | |
new_cls.__name__ = f'{name}_inner' | |
bases = [new_cls] | |
bases.extend( | |
base for base in cls.template_bases | |
if not issubclass(new_cls, base) | |
) | |
return type(name, tuple(bases), cls.template_dict) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment