Skip to content

特殊方法(魔术方法)

无所谓你如何称呼它,只要你知道它是什么即可。

我们已经知道,魔术方法是定制化一个类的行为的方法。如果你不知道,去看前面的教程哦。

实例的生命阶段

__init__

python
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()
python
Cat __init__
Fan says: Miaow!

__init__可以视为Python中的构造函数,事实上也在做着构造函数该做的事。在这里,构造函数初始化了猫的名字和年龄。

__new__

python
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()
python
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__不需要nameage这两个参数,实际上可能跟本不需要任何参数。同时考虑到后期__init__的参数可能会变更,所以你完全可以直接将__new__的参数写成cls, *args, **kwargs

如果你不知道*args, **kwargs是什么意思,去看函数的教程哦。

你可能根本不需要__new__

如果你不知道__new__对你的代码有什么帮助,那么你就不需要使用__new__

__new__的一处典型用法是实现单例模式,即让一个类在全局中只有一个实例,从不同的地方初始化会得到同一个实例。我们会在后面涉及到这方面的内容,此处暂且跳过。

__del__

python
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()
python
Cat __new__
Cat __init__
Fan says: Miaow!
Cat __del__

__del__Delete的简写,你可以把它理解为析构函数,与构造函数相对,但并不完全一致。

在上面代码的输出中,Cat __del__在最后打印,因为fan实例一直到程序运行结束才被销毁。问题并不出在__del__的执行机制,而是fan实例的销毁时间,因为Python中的对象不一定会在生命周期结束时立刻销毁,总之——一个实例在引用计数归零后,何时才被销毁是无法确定的,而__del__在对象被销毁时执行,因此不应该将需要立刻执行的代码块放在__del__中。

我记得有del关键字?

del可以用来显示“删除”一个对象,但是对象未必会在被del之后立刻销毁。简单的说,del会让对象的引用计数-1,并在安全的情况下销毁这个对象;但是如果此对象身上还有其他引用,则自然不能立刻销毁这个对象。

以下代码可以让你理解得更清楚:

python
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")
python
Cat __new__
Cat __init__
Fan says: Miaow!
Cat __del__
End of the program

在上面的代码中,fan对象(类型Cat的实例)在del fan之后就被销毁了,因为del之后,fan的引用计数归零。

python
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")
python
Cat __new__
Cat __init__
Fan says: Miaow!
End of the program
Cat __del__

现在,尽管我们del fan,但是fan依然在程序结束时才销毁,因为fan身上还有一个fan_clone的引用,fan的引用计数没有归零。

总之,不应该把重要的代码放在__del__中。

__del__方法不应该有返回值,也不应该传参。

实例的默认行为

__repr__& __str__

python
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)
python
Cat __init__
Fan says: Miaow!
Cat(name=Fan, age=3)

__repr____str__的返回值将被用来描述这个类型的实例的信息,例如以上代码在print(fan)时会打印fan的信息。

如果你只定义了__repr__或只定义了__str__,在print时会打印你定义的那个方法的返回值。如果你同时定义了这两个方法,则print会打印__str__的返回值。

你无需区分这两个方法的具体使用场景区别。

python
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__ 的返回值
python
Cat __init__
Fan says: Miaow!
Cat(name=Fan, age=3) __str__
Cat(name=Fan, age=3) __repr__

贡献者

页面历史