Last active
September 10, 2021 01:52
-
-
Save spinfish/8ebb32dd2ca593c7e8e71736872965ef to your computer and use it in GitHub Desktop.
On this day, 03/01/2020, I think I may have finally understood how metaclasses work. At least I think so. Here are some silly ones I made on that day to test out my new found knowledge.
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
from types import MappingProxyType as _Mapping | |
class _DescriptorDunderMeta(type): | |
""" | |
A metaclass that allows us to access all the builtin descriptors | |
used in the class (:class:`property`, :class:`classmethod`, | |
:class:`staticmethod`) collected into 3 groups of dunder attributes. | |
""" | |
def __new__(metacls, name, bases, attrs): | |
types = ("properties", "classmethods", "staticmethods") | |
for variable in types: | |
globals().update({variable: {}}) # Disgusting, I know | |
new_cls = super().__new__(metacls, name, bases, attrs) | |
for key, value in new_cls.__dict__.items(): | |
if key.startswith("_"): | |
continue | |
if isinstance(value, classmethod): | |
classmethods[key] = value.__func__ | |
elif isinstance(value, property): | |
properties[key] = _Mapping({ | |
attr: getattr(value, attr) | |
for attr in dir(value) | |
if not attr.startswith("_") | |
}) | |
elif isinstance(value, staticmethod): | |
staticmethods[key] = value.__func__ | |
def _property_maker(value): | |
return property(lambda _: value) | |
for prop in types: # Gross on purpose... because I can | |
setattr( | |
new_cls, | |
"__{}__".format(prop), | |
_property_maker(_Mapping(globals()[prop])) | |
) | |
return new_cls | |
class MyClass(metaclass=_DescriptorDunderMeta): | |
""" | |
A class to showcase the grouping of builtin descriptors that | |
our metaclass does via an immutable set of dunder properties. | |
""" | |
proppy = property(lambda self: "Hi, I am the {!r} property!".format(self)) | |
classmeth = classmethod(lambda cls: "Hi, I am the {!r} classmethod!".format(cls)) | |
staticmeth = staticmethod(lambda: "Hi, I am the special staticmethod!") | |
instance = MyClass() | |
print(instance.__dict__) | |
print(instance.__properties__["proppy"]["fget"](instance)) | |
print(instance.__classmethods__["classmeth"](instance.__class__)) | |
print(instance.__staticmethods__["staticmeth"]()) |
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 _list(list): | |
"""Subclass of :class:`list` to allow our metaclass to work.""" | |
remove = lambda self, item: super().remove(item) or self | |
class _NoPrivateOverrideMeta(type): | |
"""A metaclass that disallows subclasses to override "private" methods.""" | |
def __new__(metacls, name, bases, attrs): | |
def __init_subclass(cls, **kwargs): | |
for key, val in cls.__dict__.items(): | |
if not key.endswith("__") and key[0] == "_" and (any( | |
key in member for member in map( | |
lambda member: member.__dict__, | |
_list(cls.__mro__).remove(cls) | |
) | |
)): | |
raise TypeError(f"Class {cls!r} must not have overridden private method {val!r}") | |
super().__init_subclass__(**kwargs) | |
attrs["__init_subclass__"] = __init_subclass | |
return super().__new__(metacls, name, bases, attrs) | |
class NoPrivateOverride(metaclass=_NoPrivateOverrideMeta): | |
"""Base class containg a single private method.""" | |
def _private_method(self): | |
print("I am a private method that should not attempted to be overridden!") | |
class DerivedWithNoErrors(NoPrivateOverride): | |
""" | |
An example of a valid subclass of :class:`NoPrivateOverride` | |
with no overridden private methods. | |
""" | |
def _another_private_method(self): | |
print("I will exist with no errors whatsoever!") | |
class DerivedWithError(NoPrivateOverride): | |
"""An example of an invalid subclass with its private method overridden.""" | |
def _private_method(self): | |
print("You will never see me because I will never get executed!") |
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
# Think of type.__new__ like this: | |
# type.__new__(type, "MyClass", (), {}) | |
# All classes are instances of `type` by default, | |
# Meaning isinstance(MyClass, type) is True | |
# Class instances are instances of `object` | |
# Meaning isinstance(MyClass(), object) is True | |
# WARNING: Most of these are really dumb examples trying to teach how a metaclass works | |
# Reading this post really helped me with understanding them, however it took some playing | |
# Around before I understood them fully: | |
# https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/ | |
# Good luck! |
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 _SlotsMeta(type): | |
"""A metaclass that allows us to set ``__slots__`` at class definition.""" | |
def __new__(metacls, *args, slots=None): | |
new_cls = super().__new__(metacls, *args) | |
if slots is None: | |
return new_cls | |
if not isinstance(slots, (list, tuple)): | |
raise TypeError( | |
"slots must be an instance of list/tuple not {!r}".format( | |
slots.__class__ | |
)) | |
new_cls.__slots__ = slots | |
return new_cls | |
class ShowSlotsMeta(metaclass=_SlotsMeta, slots=("attr",)): | |
"""To showcase our slots metaclass.""" | |
instance = ShowSlotsMeta() | |
print(hasattr(instance, "__slots__")) | |
try: | |
instance.non_slot_attr = "test" | |
except AttributeError as error: | |
print("Encountered {!r}".format(error)) | |
instance.attr = "test" | |
################################################################################################ | |
################################################################################################ | |
class _PropertyMeta(type): | |
""" | |
A metaclass that allows us to set basic properties at class definition | |
(i.e. ones without setter and deleter methods and return a constant value). | |
Note that these overwrite the properties in the class body. | |
""" | |
def __new__(cls, *args, **properties): | |
new_cls = super().__new__(cls, *args) | |
if not properties: | |
return new_cls | |
# Thanks to SebbyLaw for teaching me about what pass by reference and pass by value mean | |
# i.e. why I can't do setattr(new_cls, property_name, property(lambda self: value)) | |
def _dummy(value): | |
return property(lambda self: value) | |
for property_name, value in properties.items(): | |
setattr(new_cls, property_name, _dummy(value)) | |
return new_cls | |
class ShowPropertyMeta(metaclass=_PropertyMeta, prop="test one", another_prop="test two"): | |
"""To showcase our properties metaclass.""" | |
instance = ShowPropertyMeta() | |
print(hasattr(instance, "prop"), hasattr(instance, "another_prop")) | |
print(instance.prop, instance.another_prop, sep="\n") | |
################################################################################################ | |
################################################################################################ | |
class _OverrideMeta(type): | |
""" | |
A very simple metaclass that allows us to overwrite specific methods | |
defined in a class body at class definition. | |
""" | |
def __new__(metacls, name, bases, attrs, **overrides): | |
attrs.update(overrides) | |
return super().__new__(metacls, name, bases, attrs) | |
class ShowOverride(metaclass=_OverrideMeta, __init__=(lambda self: print("You'll see me instead."))): | |
"""To showcase our override metaclass.""" | |
def __init__(self): | |
print("You will not see me!") | |
ShowOverride() | |
################################################################################################ | |
################################################################################################ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment