Dunder objects (any object with name __***__)#
Dunder (Double UNDERscore) objects have special meaning in Python. They define Python’s automated behavior such as item access and class initialization.
Important
You must pay attention when using dunder names, since they define Python’s language spec. Invalid modification on them can break your whole software.
On the other hand, you should modify them in sake of convenient and simple application development.
Methods to be defined by user#
__init__#
Should return nothing (None). This method is called every time you call a class
object, even if the call returns exact same instance as before (singleton pattern).
This is a good place to define instance variables the class holds, since this method is called every time a instance was created. In singleton class, you should pay attention about this feature, as it can cause unexpected initialization of the class state.
Example
Ais a class that accepts no argument on initializationclass A: def __init__(self): print("__init__ has been run")
For simple class defined above,
__init__is run every time you callA.>>> A() __init__ has been run >>> A() __init__ has been run
Ais a class that accepts arguments on initializationclass A: def __init__(self, a, b): print(f"__init__ has been run with arguments {a=}, {b=}")
In this case arguments to the class
Aimmediately passed to__init__.>>> A(1, "q") __init__ has been run with arguments a=1, b=q >>> A("abc", 123) __init__ has been run with arguments a=abc, b=123
Ais a singleton classclass A: _instance = None def __new__(cls, a, b): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def __init__(self, a, b): print(f"__init__ has been run with arguments {a=}, {b=}")
On the first call of
A, the class creates new instanceinstance1. After that, call onAreturns the exact same instance as the first one, soinstance2is not a new instance but just another name forinstance1.Even in this situation, the
__init__method is called every time you call the classA, no matter if new instance was created or not.>>> instance1 = A(1, "q") __init__ has been run with arguments a=1, b=q >>> instance2 = A("abc", 123) __init__ has been run with arguments a=abc, b=123 >>> instance1 is instance2 True
Tip
Setting a value to class variable from inside its instance is a little bit tricky. The
class variables can be accessed as self’s attribute (self.<variable_name>), but
setting a value in the same manner (self.<variable_name> = new_value) won’t modify
the class variable, instead you may define new instance variable.
Since instance variables always take precedence on self’s attribute search, the above
situation effectively obscure the class variable.
class A:
classvar = 1
def __init__(self, new_value):
print(f"{self.classvar=}")
self.classvar = new_value
print(f"{self.classvar=}")
>>> a = A(100)
self.classvar=1
self.classvar=100
>>> a.classvar
100 # Seems the class variable has been modified
>>> A.classvar
1 # But actually not
>>> a.__class__.__dict__
{'classvar': 1} # This is the class variable
>>> a.__dict__
{'classvar': 100} # This is the instance variable
__init_subclass__#
Automatically classmethod without decorator, should return nothing (None). This
method is called every time you create subclass of the class which this method is
defined on.
__new__#
Automatically classmethod without decorator, should return class instance. Returning
something which is not an instance of the class or its subclass won’t raise any error,
but the behavior differs. See below for the details.
Example
A.__new__returns an instance ofAclass A: def __new__(cls, *args, **kwargs): return super().__new__(cls) def __init__(self, *args, **kwargs): print("a")
In this case, the following 2 snippets are almost equivalent:
>>> A(1, 2, 3, a=4, b=5) a
>>> instance_of_A = A.__new__(A, 1, 2, 3, a=4, b=5) >>> instance_of_A.__init__(1, 2, 3, a=4, b=5) a
A.__new__returns an instance ofB, which is subclass ofAclass A: def __new__(cls, a, b): return super().__new__(B) def __init__(self, a, b): print(self.__class__.__name__) class B(A): ...
In this case, the behavior is almost the same as case 1, just minor swap of classes involved.
>>> A(1, 2, 3, a=4, b=5) B
>>> instance_of_B = A.__new__(A, 1, 2, 3, a=4, b=5) >>> instance_of_B.__init__(1, 2, 3, a=4, b=5) B
When class
Bis called, the behavior is exactly equivalent to case 1.>>> B(1, 2, 3, a=4, b=5) B
>>> instance_of_B = B.__new__(B, 1, 2, 3, a=4, b=5) >>> instance_of_B.__init__(1, 2, 3, a=4, b=5) B
A.__new__returns an instance ofC, which is irrelevant toAclass A: def __new__(cls, a, b): return super().__new__(C) def __init__(self, a, b): print(self.__class__.__name__) class C: ...