Skip to content

Instantly share code, notes, and snippets.

@cscutcher
Created September 5, 2019 17:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cscutcher/2e633b646154798a5590b2aecb113dd2 to your computer and use it in GitHub Desktop.
Save cscutcher/2e633b646154798a5590b2aecb113dd2 to your computer and use it in GitHub Desktop.
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