Magic method is also known as dunder method or data model method, the most basic use is to implement the __init__
, __repr__
, __str__
and other class builtin functions or plus sign +
, multiplication sign *
, greater than >=
, less than <=
and other symbolic calculations.
The naming format of the magic method is represented by two underlines in front and back. For example, summing two integers is in fact a way to implement __add__
on the int class.
All magic methods can be found in the Python Data Model documentation.
For example, we can add two int's and change the result to concatenation.
class my_int(int):
def __add__(self, x):
return int(f"{self}{x}")
a = my_int(1)
print(a + 3) # 13
When creating a class, we often use __init__
to give the class some variables. But there is a function that is executed before __init__
, called __new__
.
__new__
: It's an class level method, mainly used when you want to inherit immutable classes such asstr
,int
, etc., or when you want to implement custom metaclasses. The introduction of metaclasses can be viewed by clicking on the link!__init__
: It is called only when the constructor is executed and is used to add attributes. It's an instance level method.__del__
: It is triggered when an instance is to be deleted or recycled.
Below I create a BinaryInt
that automatically turns numbers into binary strings.
class BinaryInt(str):
def __new__(cls, val):
print(f"__new__: '{val}' -> binary int -> '{val:b}'")
return str.__new__(cls, f"{val:b}")
def __init__(self, val):
print("__init__:", self)
def __del__(self):
print("__del__:", self)
del self
a = BinaryInt(5)
print('a:', a)
# __new__: '5' -> binary int -> '101'
# __init__: 101
# a: 101
# __del__: 101
With magic method, we can take some of the non-comparable or non-calculable custom classes and make them comparable and calculable.
For example, we create a class Person
, and then use age to compare and calculate between the Person
s.
class Person:
def __init__(self, age):
self.age = age
def __add__(self, person):
return self.age + person.age
def __sub__(self, person):
return self.age - person.age
def __mul__(self, person):
return self.age * person.age
def __truediv__(self, person):
return self.age / person.age
def __floordiv__(self, person):
return self.age // person.age
def __eq__(self, person):
return self.age == person.age
def __gt__(self, person):
return self.age > person.age
def __ge__(self, person):
return self.age >= person.age
def __lt__(self, person):
return self.age < person.age
def __le__(self, person):
return self.age <= person.age
a = Person(15)
b = Person(30)
print(a + b) # 45
print(a > b) # False
print(sorted([b, a])) # [a, b]
There are two ways of expressing classes, one is __repr__
and the other is __str__
.
Very many people have different opinions about the difference between the two, But to put it bluntly, I would say __repr__
is for developers and __str__
is for customers.
class Person:
def __init__(self, age):
self.age = age
def __str__(self):
return f"A Person who is {self.age} year(s) old"
def __repr__(self):
return f"Person(age={self.age})"
a = Person(15)
print(a) # A Person who is 15 year(s) old
print(repr(a)) # Person(age=15)
print([a]) # [Person(age=15)]
Defining __call__
for a class allows the class to take arguments and turn them into something like a function that can be called.
class Person:
def __init__(self, age):
self.age = age
def __call__(self):
print(f"I'm {self.age}")
a = Person(15)
b = Person(30)
a() # I'm 15
b() # I'm 30
We can also use the magic method to customize an iterable object with arbitrary rules.
__len__
: Define the length of your iterable, can be obtained withlen()
.__getitem__
: same asself[key]
__setitem__
: same asself[key] = val
__iter__
: same asfor x in self
__reversed__
: same asreversed(self)
class MyList:
def __init__(self, values=None):
if values is None:
self.values = []
else:
self.values = values
def __len__(self):
return len(self.values)
def __getitem__(self, key):
return self.values[key]
def __setitem__(self, key, value):
self.values[key] = value
def __iter__(self):
return iter(self.values)
def __reversed__(self):
return reversed(self.values)
The class implementation methods we discussed in Context Manager also use two magic methods, __enter__
and __exit__
.
class OpenFile():
def __init__(self, filename: str, mode: str) -> object:
self.filename = filename
self.mode = mode
def __enter__(self):
self.file_obj = open(self.filename, self.mode)
return self.file_obj
def __exit__(self, except_type, except_val, except_traceback):
self.file_obj.close()
Article | Link |
---|---|
Python中的__init__和__new__ | https://www.zlovezl.cn/articles/__init__-and__new__-in-python/ |
Python Tutorial: str() vs repr() | https://www.youtube.com/watch?v=5cvM-crlDvg |