Class
class MyClass:
def __new__(cls, *args, **kwargs):
""" called upon object creation """
print('__new__')
cls.my_class_variable = 900000 # Defines class variable, not instance variable!
instance = super().__new__(cls)
return instance
def __init__(self, a, b):
""" object initialization """
print('__init__')
self.a, self.b = a, b # Defines instance variable
def __init_subclass__(self):
""" called when a child is subclassed """
print('__init_subclass__')
def __call__(self):
""" on call of object """
print('__call__')
return (self.a, self.b)
def __eq__(self, o):
""" define equality behaviour """
print('__eq__')
if isinstance(o, MyClass):
return o.a == self.a and o.b == self.b
raise TypeError('Not MyClass type')
def __gt__(self, o):
""" define greater than behaviour """
print('__gt__')
if isinstance(o, MyClass):
return o.a + o.b < self.a + self.b
raise TypeError('Not MyClass type')
def __lt__(self, o):
""" define less than behaviour """
print('__lt__')
if isinstance(o, MyClass):
return o.a + o.b > self.a + self.b
raise TypeError('Not MyClass type')
def __add__(self, o):
""" define addition behaviour """
print('__add__')
if isinstance(o, MyClass):
self.a += o.a
self.b += o.b
return self
raise TypeError('Not MyClass type')
def __sub__(self, o):
""" define addition behaviour """
print('__sub__')
if isinstance(o, MyClass):
self.a -= o.a
self.b -= o.b
return self
raise TypeError('Not MyClass type')
def __str__(self):
""" string upon printing object """
print('__str__')
return 'a:{} b:{}'.format(self.a, self.b)
def __repr__(self):
""" string upon repl output """
print('__repr__')
return 'MyClass_instance_a_b'
m1 = MyClass(10, 20)
m2 = MyClass(10, 20)
m3 = MyClass(10, 30)
# Output
# __new__
# __init__
# __new__
# __init__
# __new__
# __init__
print(MyClass.my_class_variable)
# Output
# 900000
print('\n\n---------------------------------------\n\n')
print(m1)
# Output
# __str__
# (10, 20)
print(m1 == m2)
# Output
# __eq__
# True
print(m1())
# Output
# __call__
# True
print(m1 < m3)
# Output
# __lt__
# True
print(m1 > m3)
# Output
# __gt__
# True
print(m1 + m2)
# Output
# __add__
# True
print(m1 - m2)
# Output
# __sub__
# True
print('\n\n---------------------------------------\n\n')
class Child(MyClass):
def __new__(cls, *args, **kwargs):
print('__new__Child')
cls.my_childclass_variable = 100000
instance = super().__new__(cls, *args, **kwargs)
return instance
def __init__(self, a, b):
print('__init__Child')
super().__init__(a, b)
# Subclassing MyClass calls __init_subclass__ in MyClass
# Output
# __init_subclass__
print('\n\n---------------------------------------\n\n')
c1 = Child(1, 2)
# Output
# __new__Child
# __new__
# __init__Child
# __init__
c2 = Child(1, 2)
# Output
# __new__Child
# __new__
# __init__Child
# __init__
print(Child.my_childclass_variable)
# Output
# 100000
print(c1 + c2)
# Output
# a:2 b:4
print('\n\n---------------------------------------\n\n')
class SecondChild(MyClass):
def __new__(cls, *args, **kwargs):
return 'Broke the chain of __new__'
def __init__(self, a, b):
print('__init__Child')
super().__init__(a, b)
# Subclassing MyClass calls __init_subclass__ in MyClass
# Output
# __init_subclass__
# Output
sc1 = SecondChild(1, 2)
print(sc1)
# Broke the chain of __new__
Singleton class
If __new__
returns instance of it’s own class,
then the __init__
method of newly created instance will be invoked with instance
as first(like __init__(self, [, ….]
) argument following by arguments passed to __new__
or call of class.
So, __init__
will called implicitly.
If __new__
method return something else other than instance of class,
then instances __init__
method will not be invoked.
In this case you have to call __init__
method yourself.
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = object.__new__(cls)
return cls._instance
def __init__(self, somedata):
self.somedata = somedata
def __str__(self):
return str(self.somedata)
s1 = Singleton(10)
print(s1) # 10
s1.somedata += 10
print(s1) # 20
s2 = Singleton(100)
print(s2) # 100
print(s1) # 100
Metaclasses
# Meta classes
class MyMetaClass(type):
def __new__(cls, name, bases, dct):
print('__new__MyMetaClass')
instance = super().__new__(cls, name, bases, dct)
instance.somecommon_attr = 100
cls.class_varable = 200
# Read explanation below for DerviedClass
if name == 'DerviedClass' and 'please_implement' not in dct:
raise TypeError('Bad user derived class, please_implement should be overried!')
return instance
class MyClassImpl(metaclass=MyMetaClass):
def __init__(self):
print('__init__MyClassImpl')
# Output
# __new__MyMetaClass
m = MyClassImpl()
print(m.somecommon_attr) # attr instance variable injected by MyMetaClass
print(MyClassImpl.class_varable) # attr class variable injected by MyMetaClass
# Output
# __init__MyClassImpl
# 100
# 200
class MyLibrary(metaclass=MyMetaClass):
def __init__(self):
print('__init__MyLibrary')
def please_implement(self):
pass
# Output
# __new__MyLibrary
class DerviedClass(MyLibrary):
def __init__(self):
print('__init__DerviedClass')
# Output
# TypeError: Bad user derived class, please_implement should be overried
# This is one of the most significant use cases of Meta Classes
# Since, DerviedClass inherits MyLibrary,
# it is expected that the child class should implement a please_implement() instance method
# But how do we enforce it?
# Since MyLibrary has MyMetaClass as the metaclass
# Whenever any child class inhertis MyLibrary, the MyMetaClass __ini__ is called
# Arguments to which are interestingly the type arguments
# type('', (), {})
# param 1: object or name
# param 2: Base classes
# param 3: Attribute dictionary
# Returns: new type
# Hence, we can easily determine,
# the deriving class name and methods in dict and take appropriate actions