python meta class by samples

LORY
3 min readJan 3, 2021

--

metaclass is a useful tool to control the instance creation behavior or applying field validation, enforcing naming convention ,at class creation or object creation time .

in many cases , we could archive the same by using decorator .

#1 __init__ , __new__ and __call__

to start meta class in python , lets start with __new__ __init__ and __call__ methods overrides

class MyMeta(type):
def __new__(cls, name, bases, fields):
print('calling __new__ (#1 allocating memory). only called once ')
return super(MyMeta, cls).__new__(cls, name, bases, fields)
def __init__(cls, name, bases, fields):
print('calling __init__(#2 Initialize). only called once')
return super(MyMeta, cls).__init__(name, bases, fields)
def __call__(cls, *args, **kwargs):
print('calling __call__ (#3 called when instantiate a object)')
return type.__call__(cls, *args, **kwargs)
class X(metaclass=MyMeta):
pass
calling __new__ (#1 allocating memory). only called once
calling __init__(#2 Initialize). only called once
calling __call__ (#3 called when instantiate a object)
calling __call__ (#3 called when instantiate a object)

Conclusion — __init__ and __new__ called only once when class created . __call__ being called whenever a new object got created .

#2 Add “meta” fields to class

Now we want to ‘inject’ class_id and object_id field into every class using this metaclass .

import uuid 
class ClassMeta(type):
def __new__(cls, name, bases, fields):
cls.class_id = uuid.uuid4().hex
res = super().__new__(cls, name, bases, fields)
return res
def __call__(cls, *args, **kwargs):
obj = type.__call__(cls, *args, **kwargs)
obj.unique_id = uuid.uuid4().hex
return obj
class X(metaclass=ClassMeta):
pass
if __name__ == "__main__":
print(X.class_id)
print(X().unique_id)
print(X().unique_id)
if __name__ == "__main__":
print(X.unique_id)
print(Y.unique_id)
output :
859c5f93f1344a78a7be2bf7a2eacba8
a81f6d47212d43919a9779a8585ecc91
ce88876587fc4619ba9fa0401cfaffa2

__new__ only called when class got created ,could be used to add class level fields ;__call__ could be used to add object level fields ,context is within each individual object

#3 Count how many objects in memory

Sometimes we want to measure application performance .by using metaclass + weakref we could get class_name: <num of objects> to make usage of memory more effetive .

import weakref
class ObjCount(type):
obj_count = {}
def __call__(cls, *args, **kwargs):
print(str(cls))
cls_name = str(cls)
ObjCount.obj_count.setdefault(cls_name, [])
obj = type.__call__(cls, *args, **kwargs)
ObjCount.obj_count[cls_name].append(weakref.ref(obj))
return obj
@staticmethod
def stat():
for k,v in ObjCount.obj_count.items():
print(f'class {k} : created {len([x for x in v if x()])} objects')
class C(metaclass=ObjCount):
pass
class C2(metaclass=ObjCount):
pass
if __name__ == "__main__":
c1,c2,c3 = C(), C(), C()
c4,c5 = C2(), C2()
ObjCount.stat()
# class <class '__main__.C'> : created 3 objects
# class <class '__main__.C2'> : created 2 objects
del c1
ObjCount.stat()
#class <class '__main__.C'> : created 2 objects
#class <class '__main__.C2'> : created 2 objects
del c2
del c4
ObjCount.stat()
# class <class '__main__.C'> : created 1 objects
# class <class '__main__.C2'> : created 1 objects

#4 enforce naming convention

meta data class could be useful to enforce (field or method) naming convention ,for example you could enforce field naming to use snake case .

class EnforceSnakeCase(type):
def __new__(meta, names, bases, fields):
attrs = {}
for name, val in fields.items():
attrs[to_snake_case(name)] = val
return type(names, bases, attrs)
class X(metaclass=EnforceSnakeCase):
intField=3
strField="abc"
def __repr__(self):
return f'{self.int_field} {self.str_field}'
if __name__ == "__main__":
x = X()
#print(x.intField) # error
# AttributeError: 'X' object has no attribute 'intField'
x.int_field = 15
x.str_field="dsa"
print(x)

#5 abstract base class

though not a big fan of OOP . but meta could be used to implement abstract class or method .sometimes ,useful to structure your code specially when you building a UI framework or get “abstraction templated”

from abc import ABCMeta, abstractmethod
class UIControl(metaclass=ABCMeta):
@abstractmethod
def created(self):
print(f'{type(self)} created')
@abstractmethod
def destroy(self):
print(f'{type(self)} destroyed')
class Button(UIControl):
pass
if __name__ == "__main__":
Button()

because base class provided abstract method to enforce child to implement .without doing it will result in below error
error :
Button()
TypeError: Can't instantiate abstract class Button with abstract methods created, destroy

Provide implementation in child class to fix it .

class Button(UIControl):

def created(self):
super().created()
print('button created')
def destroy(self):
super().destroy()
print('button destroyted')
if __name__ == "__main__":
Button().created()
output :
<class '__main__.Button'> created
button created

--

--

LORY
LORY

Written by LORY

A channel which focusing on developer growth and self improvement

No responses yet