Special methods#
A class can hold methods that define its behavior in common cases in Python. These methods usually start and end with double underscores (__<name>__).
Indexing#
There are three methods that organise how the class would behave when the indexing operator [] is applied to the instance: __getitem__, __setitem__, __delitem__. Their names clearly describe the idea behind them.
The next cell shows the implementation of the class that shows the appropriate information for each of the methods.
class Test:
def __setitem__(self, key, value):
print(f"Set item called with {key} - {value}")
def __getitem__(self, key):
print(f"getitem is called with {key}")
return "dummy item"
def __delitem__(self, key):
print(f"delitem is called with {key}")
And here is the creation of the instance and all the manipulations that trigger the methods that we are now focusing on.
test = Test()
test[40] = 4
print(test["wow"])
del test[3.14]
Set item called with 40 - 4
getitem is called with wow
dummy item
delitem is called with 3.14
Note: if you want to handle slices in your objects just write code to handle python slice object. The following code passes 3: to the indexing operator.
Test()[3:]
getitem is called with slice(3, None, None)
'dummy item'
The result there is the slice object passed to the corresponding method.
Iteration protocol#
To organise ability to iterate over your object you basically need to define the __iter__ method. It will be called each time the object is passed to the iter build in function or used in for cycle.
The returned object is typically have __next__. This method is called on every iteration. In particular case __iter__ can return self, but for that case __next__ have to be defined in the class.
Note: python understands when to stop iteration by raising StopIteration exception. So you need to raise it inside the __next__ method when object needs to stop iterations according to your logic.
Check more at corresponding iterator section of the official documentation.
The following cell implements the class that will have the specified during creation number of iterations.
class IterationExample:
def __init__(self, max_iterations):
self.iteration = 0
self.max_iterations = max_iterations
def __iter__(self):
print("__iter__ is called")
return self
def __next__(self):
print("__next__ is called")
if self.iteration >= self.max_iterations:
print("Stop iteration is raised")
raise StopIteration
self.iteration += 1
return self.iteration
Now apply the for cycle to the instance of that class.
for i in IterationExample(3):
pass
__iter__ is called
__next__ is called
__next__ is called
__next__ is called
__next__ is called
Stop iteration is raised
Note __iter__ that retuns objects that do’t implement __next__ make no sence, as python checks this detail.
class Fail:
def __iter__(self):
return self
iter(Fail())
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[50], line 5
2 def __iter__(self):
3 return self
----> 5 iter(Fail())
TypeError: iter() returned non-iterator of type 'Fail'
Instance creation#
The __new__ class method is invoked when an instance is created. The return value of __new__ is what will be returned when the class is called to create a new instance. To implement standard Python behavior, it should return the result of object.__new__(cls).
Check the official description in the corresponding section.
The following cell defines a class whose __new__ and __init__ method indicate that they have been invoked.
class SomeClass:
def __new__(cls):
print("new invoked:", type(cls))
return super().__new__(cls)
def __init__(self):
print("init invoked:", type(self))
The instance creation.
SomeClass()
new invoked: <class 'type'>
init invoked: <class '__main__.SomeClass'>
<__main__.SomeClass at 0x791358ce06e0>
In fact, for SomeClass the syntax super().__new__(cls) is equivalent to the syntax object.__new__(SomeClass). That’s why tricks like the following works:
object.__new__(SomeClass)
<__main__.SomeClass at 0x791359721e50>
The object.__new__ creates the instance of the passed class.
The __new__ determines the output a class invocation. The following cell defines a class that cannot be created using the regular syntax:
class StrClass:
def __new__(cls):
return "hello"
An attempt to create an instance returns the "hello" string, as specified in the __new__ method.
StrClass()
'hello'
But you can still can directly call the object.__new__ to create an instance.
object.__new__(StrClass)
<__main__.StrClass at 0x791358ce0980>
Switch number system#
There is a special __index__ special method that determines the behaviour of the object if programm tries to apply bin, oct or hex fucntions to it. __index__ just have to return regular python integer and converting of that number to the corresponding number system will be the result.
The following cell defines the class that returns from the index number specified in the constructor of the object.
class NSTest:
def __init__(self, my_number):
self.my_number = my_number
def __index__(self):
return self.my_number
The following code shows the result for different number systems and input numbers.
bin(NSTest(3))
'0b11'
oct(NSTest(10))
'0o12'
hex(NSTest(12))
'0xc'
Init subclass#
The __init_subclass__ will be called each time a new class subclassing the class containing is created.
The following cell creates a class that implements the __init_subclass__ method so that the name of the created class is printed out.
class A:
def __init_subclass__(cls) -> None:
print(f"The class {cls.__name__} is created.")
The next code subclasses the B class.
class B(A):
pass
The class B is created.
The corresponding message appearsl.
The second step child C works the same way as the B class:
class C(B):
pass
The class C is created.
Subclass kwargs#
If you define the keyword arguments in the __init_subclass__ method, they must be passed in the child class’s definition.
The following cell defines the Base class, which __init_subclass__ requires some_value key word argument.
class Base:
def __init_subclass__(cls, /, some_value: str) -> None:
print(f"Got some value {some_value}!")
Got some value Hello from Child!
As a result, the attempt to subclass the Base fails.
class Child(Base):
pass
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[12], line 1
----> 1 class Child(Base):
2 pass
TypeError: Base.__init_subclass__() missing 1 required positional argument: 'some_value'
But if pass the some_value, everything goes well, and some_value will be used as defined in the __init_subclass__.
class Child(Base, some_value="Hello form Child"):
pass
Got some value Hello form Child!