Skip to content

Instantly share code, notes, and snippets.

@TheTrio
Created July 18, 2022 17:26
Show Gist options
  • Save TheTrio/4c9b3e3a0d7854ac0c77ef752d9edbee to your computer and use it in GitHub Desktop.
Save TheTrio/4c9b3e3a0d7854ac0c77ef752d9edbee to your computer and use it in GitHub Desktop.
A dummy dataclasses like system implemented from scratch using Metaclasses
from typing import Any, Callable
class NoDefault:
...
NO_DEFAULT = NoDefault()
class Descriptor:
def __init__(self, name, kind, default: Callable | NoDefault) -> None:
self.name = name
self.kind = kind
self.default = default
def __get__(self, instance, owner):
if self.name not in instance.__dict__ and self.default is not NO_DEFAULT:
return self.default() # type: ignore
return instance.__dict__[self.name]
def __set__(self, instance, value):
if isinstance(value, self.kind):
instance.__dict__[self.name] = value
return
raise TypeError(
f"Expected '{self.kind.__name__}' got '{type(value).__name__}' for {self.name}"
)
class Type:
def __init__(self, kind, **kwargs) -> None:
self.kind = kind
if "default" in kwargs:
self.default = kwargs["default"]
else:
self.default = NO_DEFAULT
class MetaClass(type):
def __new__(cls, name, bases, attrs):
new_attrs = {}
for key, value in attrs.items():
if isinstance(value, Type):
new_attrs[key] = Descriptor(key, value.kind, value.default)
else:
new_attrs[key] = value
return super().__new__(cls, name, bases, new_attrs)
def __call__(self, *args: Any, **kwargs: Any) -> Any:
params = {
key: value
for key, value in self.__dict__.items()
if isinstance(value, Descriptor)
}
if (len(args) + len(kwargs)) > len(params):
raise TypeError("Too many arguments")
items = iter(params.items())
for arg in args:
key, _ = next(items)
if key in kwargs:
raise TypeError(
f"Duplicate argument - {key} passed both by position and by name"
)
kwargs[key] = arg
for param in items:
key, value = param
if key not in kwargs and value.default is NO_DEFAULT:
raise TypeError(f"Missing argument - {key}")
def __init__(instance, *vargs, **kvargs):
for key, value in kwargs.items():
setattr(instance, key, value)
self.__init__ = __init__
return super().__call__()
class MyDataClass(metaclass=MetaClass):
...
class Base(MyDataClass):
name = Type(str)
age = Type(int, default=lambda: 18)
b = Base("Shashwat")
print(b.name)
print(b.age)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment