特殊方法(魔术方法)
无所谓你如何称呼它,只要你知道它是什么即可。
我们已经知道,魔术方法是定制化一个类的行为的方法。如果你不知道,去看前面的教程哦。
实例的生命阶段
__init__
class Cat:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
print("Cat __init__")
def miaow(self):
print(f"{self.name} says: Miaow!")
fan = Cat("Fan", 3)
fan.miaow()Cat __init__
Fan says: Miaow!__init__可以视为Python中的构造函数,事实上也在做着构造函数该做的事。在这里,构造函数初始化了猫的名字和年龄。
__new__
class Cat:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
print("Cat __init__")
def __new__(cls, *args, **kwargs):
print("Cat __new__")
return super().__new__(cls)
def miaow(self):
print(f"{self.name} says: Miaow!")
fan = Cat("Fan", 3)
fan.miaow()Cat __new__
Cat __init__
Fan says: Miaow!那__new__干了什么事?根据输出可知,__new__在__init__之前执行,实际上上面代码中的fan这只猫是在__new__时被实例化的,然后由__init__进行初始化。
__new__接收的cls参数是类型Cat,返回的是一个Cat的实例。super().__new__(cls)调用了Cat父类的__new__,在Python3中所有的类都隐式继承自object,关于super的详细用法会在类的继承中介绍。
由于__new__实现的功能,你必须让__new__返回一个实例。上面的代码中使用的是通用写法。
为什么要用*args, **kwargs?
在fan = Cat("Fan", 3)时,Python先后执行Cat的__new__和__init__方法,对__new__传入的参数是类型Cat加上给出的"Fan"和3,对__init__传入的参数是Cat的实例加上给出的"Fan"和3。
因此,__new__必须拥有和__init__相同的签名,否则Python会在运行时报错TypeError,显示Cat.__new__的参数数量错误。
但是大部分时候,比如本例中,你的__new__不需要name和age这两个参数,实际上可能跟本不需要任何参数。同时考虑到后期__init__的参数可能会变更,所以你完全可以直接将__new__的参数写成cls, *args, **kwargs。
如果你不知道*args, **kwargs是什么意思,去看函数的教程哦。
你可能根本不需要__new__
如果你不知道__new__对你的代码有什么帮助,那么你就不需要使用__new__。
__new__的一处典型用法是实现单例模式,即让一个类在全局中只有一个实例,从不同的地方初始化会得到同一个实例。我们会在后面涉及到这方面的内容,此处暂且跳过。
__del__
class Cat:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
print("Cat __init__")
def __new__(cls, *args, **kwargs):
print("Cat __new__")
return super().__new__(cls)
def __del__(self):
print("Cat __del__")
def miaow(self):
print(f"{self.name} says: Miaow!")
fan = Cat("Fan", 3)
fan.miaow()Cat __new__
Cat __init__
Fan says: Miaow!
Cat __del____del__即Delete的简写,你可以把它理解为析构函数,与构造函数相对,但并不完全一致。
在上面代码的输出中,Cat __del__在最后打印,因为fan实例一直到程序运行结束才被销毁。问题并不出在__del__的执行机制,而是fan实例的销毁时间,因为Python中的对象不一定会在生命周期结束时立刻销毁,总之——一个实例在引用计数归零后,何时才被销毁是无法确定的,而__del__在对象被销毁时执行,因此不应该将需要立刻执行的代码块放在__del__中。
我记得有del关键字?
del可以用来显示“删除”一个对象,但是对象未必会在被del之后立刻销毁。简单的说,del会让对象的引用计数-1,并在安全的情况下销毁这个对象;但是如果此对象身上还有其他引用,则自然不能立刻销毁这个对象。
以下代码可以让你理解得更清楚:
class Cat:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
print("Cat __init__")
def __new__(cls, *args, **kwargs):
print("Cat __new__")
return super().__new__(cls)
def __del__(self):
print("Cat __del__")
def miaow(self):
print(f"{self.name} says: Miaow!")
fan = Cat("Fan", 3)
fan.miaow()
del fan
print("End of the program")Cat __new__
Cat __init__
Fan says: Miaow!
Cat __del__
End of the program在上面的代码中,fan对象(类型Cat的实例)在del fan之后就被销毁了,因为del之后,fan的引用计数归零。
class Cat:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
print("Cat __init__")
def __new__(cls, *args, **kwargs):
print("Cat __new__")
return super().__new__(cls)
def __del__(self):
print("Cat __del__")
def miaow(self):
print(f"{self.name} says: Miaow!")
fan = Cat("Fan", 3)
fan.miaow()
fan_clone = fan
del fan
print("End of the program")Cat __new__
Cat __init__
Fan says: Miaow!
End of the program
Cat __del__现在,尽管我们del fan,但是fan依然在程序结束时才销毁,因为fan身上还有一个fan_clone的引用,fan的引用计数没有归零。
总之,不应该把重要的代码放在__del__中。
__del__方法不应该有返回值,也不应该传参。
实例的默认行为
__repr__& __str__
class Cat:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
print("Cat __init__")
def __repr__(self):
return f"Cat(name={self.name}, age={self.age})"
def miaow(self):
print(f"{self.name} says: Miaow!")
fan = Cat("Fan", 3)
fan.miaow()
print(fan)Cat __init__
Fan says: Miaow!
Cat(name=Fan, age=3)__repr__和__str__的返回值将被用来描述这个类型的实例的信息,例如以上代码在print(fan)时会打印fan的信息。
如果你只定义了__repr__或只定义了__str__,在print时会打印你定义的那个方法的返回值。如果你同时定义了这两个方法,则print会打印__str__的返回值。
你无需区分这两个方法的具体使用场景区别。
class Cat:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
print("Cat __init__")
def __repr__(self):
return f"Cat(name={self.name}, age={self.age}) __repr__"
def __str__(self):
return f"Cat(name={self.name}, age={self.age}) __str__"
def miaow(self):
print(f"{self.name} says: Miaow!")
fan = Cat("Fan", 3)
fan.miaow()
print(fan)
print(repr(fan)) # 可以使用内置函数 repr() 来打印 __repr__ 的返回值Cat __init__
Fan says: Miaow!
Cat(name=Fan, age=3) __str__
Cat(name=Fan, age=3) __repr__

MangoFanFan_