Creation of a metaclass for dataclass.

Kirill Balunov kirillbalunov at gmail.com
Fri Dec 29 04:34:08 EST 2017


I'm studying the meta-programming topic in Python, and as an exercise I'm
trying to make a somewhat simplified version of dataclass. My goal in this
exercise is to make the difference between annotated variables and usual
ones to be as much transparent as possible. So I come with this code to
obtain initial fields. It does not catch corner cases and as much as
possible undressed to be short. So my question is there any visible
pitfalls or am I doing something wrong from the early beginning?

Two helpful functions not to clutter `__annotations__`:

from typing import Any
AnyType = Any

def _is_not_dunder(name):
    return not ((len(name) > 4) and (name[:2] == name[-2:] == '__'))

def _is_not_descriptor(obj):
    return not (hasattr(obj, '__get__') or
                hasattr(obj, '__set__') or
                hasattr(obj, '__delete__'))

The special dict (class namespace) to inject usual variables in `
__annotations__` with default typehint - `AnyType`, and also keep their
ordered appearance in the class body.

class SpecialDict(dict):
    def __init__(self):
        super().__init__()
        super().__setitem__('__annotations__', {})

    def __setitem__(self, key, value):
        if not (key in self) and _is_not_dunder(key) and
_is_not_descriptor(value):
            self['__annotations__'][key] = AnyType
        super().__setitem__(key, value)

Data meta-class which defines `__fields` from `__annotations__`:

class DataMeta(type):
    @classmethod
    def __prepare__(metacls, cls, bases, **kwargs):
        _dict = SpecialDict()
        return _dict

    def __init__(cls, *args , **kwargs):
        super().__init__(*args)

    def __new__(metacls, cls, bases, clsdict):
        clsdict['__fields'] = tuple(clsdict['__annotations__'].items())
        _cls = type.__new__(metacls, cls, bases, clsdict)
        return _cls

And test class:

class MyClass(metaclass=DataMeta):
    a: float
    barattr: int = 2
    jik = 12
    bzik: int =14

    def foo(self, param):
        pass

data = MyClass()

a.__fields   # (('a', float), ('barattr', int), ('jik', typing.Any),
('bzik', int))


Thank you!

With kind regards, -gdg



More information about the Python-list mailing list