python中的魔术方法(特殊方法)

1. 前言

  python的内置方法具有特殊的功能,这些内置方法我们也称之为魔术方法(magic method)或特殊方法(special method)。魔术方法就是前后各有两个下划线__的方法,像__init__方法就是一个魔术方法。python中的类提供了很多双下划线开头和结尾__xxx__的方法,这些方法是Python运行的基础,很多功能的背后都是通过调用这些内置方法来实现的。例如len()函数调用对象的__len__方法;print(obj)函数调用对象的__str__方法;for循环遍历语句for item in iterable_obj调用对象的__next____iter__方法。下面对常见的魔术方法进行介绍。
在这里插入图片描述
上面的图片来自于:【python】魔术方法大全——基础篇

2. __init__方法

  __init__方法是一个构造方法,在创建类对象的时候用于初始化的设置。__init__方法在创建类的实例对象时自动调用

class Student:
    def __init__(self,name,age,id) -> None:
        self.name = name
        self.age = age
        self.id = id

    def show_info(self) -> None:
        print(f"name = {self.name},age = {self.age},id = {self.id}")
    
stu = Student("zhangsan",18,"001")  # 会自动调用__init__方法
stu.show_info() # 输出:name = zhangsan,age = 18,id = 001

3. __new__方法

  记住重要的一点:__new__方法用于创建对象实例,而__init__方法用于实例对象的初始化,且__new__方法在__init__方法之前被调用。也就是说,我们必须先创建对象(先调用__new__方法),然后才可以进行对象的初始化(再调用__init__方法)。
(1)__new__方法负责实例对象的创建,在对象实例化的时候,它是第一个被调用的方法。
(2)__new__是一个类方法(类方法使用​​cls​​作为第一个参数,cls==class(类)),因此在调用__new__方法时使用类本身而不是实例对象。
(3)__new__方法必须返回一个实例对象,这个实例对象通常是由super().__new__(cls)​​​创建的,即调用父类的__new__​​方法。通常情况下,你不需要直接调用__new__方法,因为它在实例化时自动被调用。
(4)当构造方法__init__是一个无参数构造时,在__new__方法中无需传递参数,并使用super().__new__(cls)来创建实例对象。如下所示:​​​

class Student:
    def __new__(cls):
        instance = super().__new__(cls)
        print("__new__被执行了...")
        return instance
    
    def __init__(self) -> None:
        print("__init__被执行了...")
    
stu = Student()

输出结果:
__new__被执行了...
__init__被执行了...

(5)当构造方法__init__是一个有参数构造时,在__new__方法中需要传递相应的参数,并使用super().__new__(cls)来创建实例对象。如下所示:

class Student:
    def __new__(cls,name,age,id):
        # instance = super(Student,cls).__new__(cls,name,age,id) # python2写法,会报错
        # 下面是python3的写法
        instance = super().__new__(cls)
        print("__new__被执行了...")
        return instance
    
    def __init__(self,name,age,id) -> None:
        self.name = name
        self.age = age
        self.id = id
        print("__init__被执行了...")
    
stu = Student("zhangsan",18,"001")

输出结果:
__new__被执行了...
__init__被执行了...

4. __call__方法

  __call__方法的作用是把一个类的实例化对象变成可调用对象。例如:在对象进行调用方法时,实例对象.__call__()等价于实例对象()。我们可以使用内置函数callable(obj)来判断对象obj是否为可调用对象。

class Student:
    def __init__(self,name,age,id) -> None:
        self.name = name
        self.age = age
        self.id = id

    def __call__(self, str1: str, str2: str):
        print(f"str1+str2 = {str1+str2}")
    
stu = Student("zhangsan",18,"001")  # 会自动调用__init__方法
stu.__call__("hello ","world")  # 输出:str1+str2 = hello world
stu("hello ","world")  # 输出:str1+str2 = hello world
print(callable(stu)) # 输出:True

5. __str__方法

  __str__方法的作用是把一个类的实例对象变成字符串(str)。
(1)不使用__str__方法:返回实例对象的内存地址。

class Student:
    def __init__(self,name: str, age: int, id: str) -> None:
        self.name = name
        self.age = age
        self.id = id

stu = Student("zhangsan", 18, "001")
print(stu)  # 输出:<__main__.Student object at 0x0000020E72F6E088>

(2)使用__str__方法:返回一个字符串。

class Student:
    def __init__(self,name: str, age: int, id: str) -> None:
        self.name = name
        self.age = age
        self.id = id

    def __str__(self) -> str:
        return f"name = {self.name}, age = {self.age}, id = {self.id}"

stu = Student("zhangsan", 18, "001")
print(stu)  # 输出:name = zhangsan, age = 18, id = 001

(3)由上面代码可知:当使用__str__方法时,输出实例对象(print(stu))的结果由<__main__.Student object at 0x0000020E72F6E088>变成name = zhangsan, age = 18, id = 001。即:__str__方法的作用是把一个类的实例对象变成字符串(str)。

6. __repr__方法

  __repr__方法用来返回一个实例对象的字符串(str)表示形式。

class Student:
    def __init__(self,name: str, age: int, id: str) -> None:
        self.name = name
        self.age = age
        self.id = id

    def __repr__(self) -> str:
        return f"Student(name='{self.name}',age={self.age},id='{self.id}')"

stu = Student("zhangsan", 18, "001")
#当我们给repr()函数传入对象时,会调用__repr__方法。
print(repr(stu))  # 输出:Student(name='zhangsan',age=18,id='001')

__str__方法和__repr__方法的相同点和不同点

(1)这两个方法都可以用来输出实例对象的字符串表示形式。
(2)当我们打印一个实例对象时,Python会自动调用__str__方法。如果该对象没有实现(或定义)__str__方法,Python会寻找对象的__repr__方法。如果该对象也没有实现__repr__方法,则输出默认的对象表示形式(返回实例对象的内存地址)。如果我们需要显式的指定以何种方式进行实例对象到字符串的转化,可以使用内置的str()repr()函数,它们会调用类中对应的双下划线方法。也就是说str()函数会调用__str__方法,repr()函数调用__repr__方法。参考下面代码:

class Student:
    def __init__(self,name: str, age: int, id: str) -> None:
        self.name = name
        self.age = age
        self.id = id

    def __str__(self) -> str:
        return f"执行__str__..., name = {self.name}"
        
    def __repr__(self) -> str:
        return f"执行__repr__..., name = {self.name}"

stu = Student("zhangsan", 18, "001")
print(stu)  # 输出:执行__str__..., name = zhangsan。自动调用__str__方法
print(str(stu))  # 输出:执行__str__..., name = zhangsan。调用__str__方法
print(repr(stu))  # 输出:执行__repr__..., name = zhangsan。调用__repr__方法

(3)__repr__方法的返回结果更多地用于开发者调试和重新创建对象,并且可以准确地重现该对象的状态和属性。而__str__方法的返回结果对用户更加友好,提供一个易于理解的字符串表示 。参考下面代码:

import datetime
today = datetime.datetime.today()
print(str(today))  # 输出:2024-08-05 11:17:11.422536
print(repr(today))  #输出:datetime.datetime(2024, 8, 5, 11, 17, 11, 422536)

"""
(1)__str__ 的返回结果可读性强。也就是说,__str__ 的意义是得到便于人们阅读的信息,
   就像上面的 "2024-08-05 11:17:11.422536" 一样。
(2)__repr__ 的返回结果应更准确。__repr__ 存在的目的在于调试,便于开发者使用。将__repr__ 返回的结果
  datetime.datetime(2024, 8, 5, 11, 17, 11, 422536) 直接复制到命令行上,是可以直接执行的。
"""

(4)我们在写类的时候,最好至少添加一个__repr__方法来保证实例对象到字符串的转换具有自定义的有效性。__str__是可选的,因为在默认情况下,__str__方法默认调用__repr__方法,所以在实例对象转字符串的时候,找到底层__str__方法之后,会调用重写__repr__方法。

参考文章:浅谈python中__str__和__repr__的区别

7. __getitem__方法

   __getitem__方法,用于索引和切片操作,允许我们通过索引或切片的方式访问对象的元素。__getitem__方法返回所给键对应的值。当对象是序列类型(像列表、元组和字符串)时,键是整数;当对象是映射(字典)时,键是任意值。使用场景是:在定义类时,如果希望能够按照键取类的值,则需要定义__getitem__方法
   在Python中,可使用索引器运算符[]来访问对象元素,例如:我们定义了一个列表mylist = [2,4,6,8],然后可以通过索引器运算符[]来访问列表对象的元素,即my_list[2]。其实,my_list[2]大致等价于my_list.__getitem__(2)。同理,我们定义了一个字典my_dict = {"name": "ZS", "age": 18},那么my_dict['name']大致等价于my_dict.__getitem__("name")。利用索引器运算符[]来访问列表和字典对象元素的代码如下:

my_list = [2,4,6,8]
my_dict = {"name": "ZS", "age": 18}

print(my_list[2])  # 输出:6
print(my_list.__getitem__(2))  # 输出:6
print(my_dict["name"])  # 输出:ZS
print(my_dict.__getitem__("name"))  # 输出:ZS

   当传递给索引器运算符[]的参数不止一个时,那么这些参数会被隐式的转换成元组。例如:列表切片my_list[1:4]等价于my_list[slice(1,4)]my_list[1:4, 0]等价于my_list[ (slice(1,4), 0)]
   (1)当对象是序列类型(像列表、元组和字符串)时,键是整数,使用__getitem__方法来实现一个自定义的可索引对象。

class Student:
    def __init__(self, *args) -> None:
        # args是一个元组类型,它可以接收可变数量的参数。
        print(args)  # 输出:(2, 4, 6, 8, 10)
        print(type(args))  # 输出:<class 'tuple'>
        self.my_list = list(args)

    def __getitem__(self, item):
        print("__getitem__被执行了...")
        print(type(item))
        return self.my_list[item]
        
stu = Student(2,4,6,8,10)
# 通过实现__getitem__方法,我们可以使用索引或切片操作来获取my_list列表中的元素
print(stu[1])  # 输出:"__getitem__被执行了..."、<class 'int'> 和 4
print(stu[1:4]) # 输出:"__getitem__被执行了..."、<class 'slice'> 和 [4, 6, 8]

# stu[1] 等价于 stu.__getitem__(1)
# stu[1:4] 等价于 stu.__getitem__(slice(1,4))

  (2)当对象是映射(字典)时,键是任意值。使用__getitem__方法来实现一个自定义的可索引对象。

class Student:
    def __init__(self, **kwargs) -> None:
        # kwargs是一个字典类型,它可以接收以键-值对形式传递的可变数量的参数。
        print(kwargs)  # 输出:{'name': 'zs', 'age': 18, 'id': '001'}
        print(type(kwargs))  # 输出:<class 'dict'>
        self.my_dict = dict(kwargs)

    def __getitem__(self, item):
        print("__getitem__被执行了...")
        print(type(item))
        if isinstance(item, tuple):
            """判断item是否为元组类型的对象,如果是的话,则通过列表推导式
            将字典中键(key)对应的值(value)存放到列表中,并返回该列表。
            """
            return [self.my_dict[key] for key in item]
        else:
            return self.my_dict[item]
        
stu = Student(name="zs", age = 18, id="001")

print(stu["name"])  # 输出:"__getitem__被执行了..."、<class 'str'>、zs
print(stu["name","age","id"])  # stu["name","age","id"]等价于stu[("name","age","id")],参数被转换成元组。
 # 输出:"__getitem__被执行了..."、<class 'tuple'>、['zs', 18, '001']

参考文章:Python中__getitem__()方法和索引器[]的详细用法

8. __setitem__方法

   __setitem__方法的作用是让类按照一定的方法存储和键(key)映射的值(value),该值可以使用__getitem__方法来获取。使用场景:当期望定义的类具备按照键存储值时,即类能够执行obj[“key”]=value(等价于obj.__setitem__(key,value))。代码示例如下:

class Student:
    def __init__(self, **kwargs) -> None:
        self.my_dict = dict(kwargs)

    def __getitem__(self, item):
        print("__getitem__被执行了...")
        if isinstance(item, tuple):
            return [self.my_dict[key] for key in item]
        else:
            return self.my_dict[item]

    def __setitem__(self, key, value):
        print("__setitem__被执行了...")
        self.my_dict[key] = value

    def __str__(self) -> str:
        obj_str = str()
        # 遍历字典中的键值对
        for key, value in self.my_dict.items():
            obj_str = obj_str + f"{key} = {value} "
        return obj_str

stu = Student(name="zs", age = 18)
print(stu)  # 输出:name = zs age = 18 
# 调用__setitem__方法, stu["id"] = "001"等价于stu.__setitem__("id","001")
stu["id"] = "001" # 输出:"__setitem__被执行了..."
print(stu) # 输出:name = zs age = 18 id = 001 
print(stu["id"])  # 输出:"__getitem__被执行了..."、001

9. __delitem__方法

  __delitem__方法用于删除给定键对应的元素。使用del关键字来删除指定键对应的元素,即del obj[key],等价于obj.__delitem__(key)

class Mylist:
    def __init__(self, *args) -> None:
        self.obj_list = list(args)

    def __delitem__(self, key):
        print("__delitem__被调用...")
        del self.obj_list[key]

obj = Mylist(1,2,3,4,5,6)
print(obj.obj_list) # 输出:[1, 2, 3, 4, 5, 6]
del obj[2] # 输出:__delitem__被调用...
print(obj.obj_list) # 输出:[1, 2, 4, 5, 6]

10. __len__方法

  __len__方法用于返回对象的长度或元素个数。我们可以在类中定义__len__方法来实现对该类的对象使用 len()函数。

class Mylist:
    def __init__(self, *args) -> None:
        self.obj_list = list(args)

    def __len__(self):
        print("__len__被调用...")
        return len(self.obj_list)

obj = Mylist(1,2,3,4,5,6)
print(len(obj))  # 输出:"__len__被调用..."、6
print(obj.__len__())  # 输出:"__len__被调用..."、6
# len(obj)等价于obj.__len__()

11. 富比较特殊方法

  富比较(rich comparison)特殊方法是一组用于实现对象之间比较操作的特殊方法。这些方法使得自定义对象可以支持 <, <=, ==, !=, >=, > 这样的比较操作。
在这里插入图片描述
在这里插入图片描述
参考文章:Python 富比较特殊方法

  下面以__lt__方法和__eq__方法为例,代码如下:

class Student:
    def __init__(self,name,age,id) -> None:
        self.name = name
        self.age = age
        self.id = id

    def __lt__(self, other: object) -> bool:
        # 使用对象中的age属性来比较大小
        return self.age < other.age
    
    def __eq__(self, other: object) -> bool:
        # 使用对象中的age属性来比较大小
        return self.age == other.age

    
stu1 = Student("zhangsan",18,"001")
stu2 = Student("zhangsan",30,"001")
print(stu1<stu2)  # 等价于:stu1.__lt__(stu2)。输出:True
print(stu1 == stu2)  # 等价于:stu1.__eq__(stu2)。输出:False

12. __iter__方法和__next__方法

  首先我们需要知道什么是可迭代对象(iterable)和迭代器(iterator)。但可迭代对象和迭代器的概念太抽象了,容易把我搞懵,那怎么办呢。其实我们可以使用python的内置函数isinstance(obj, classinfo)来查询某个对象(obj)是否为指定的类型(classinfo),该函数的返回值为TrueFalse

from collections.abc import Iterable, Iterator
# 需要导入Iterable, Iterator
obj_list = [1,2,3,4]
obj_tuple = (1,2,3)
print(isinstance(obj_list, Iterable)) # 输出:True
print(isinstance(obj_list, Iterator)) # 输出:False
print(isinstance(obj_tuple, Iterable)) # 输出:True
print(isinstance(obj_tuple, Iterator)) # 输出:False

从上面的代码可知:列表、元组都是可迭代对象(iterable),都不是迭代器对象(iterator)。下面就对可迭代对象和迭代器进行介绍:

  (1)可迭代对象(iterable):如果一个对象实现了__iter__方法,那么这个对象就是可迭代对象。常见的可迭代对象有:列表(list)、元组(tuple)、字典(dict)和文件对象等等。下面代码中,在MyList类中实现了__iter__方法,所以对象obj_list是可迭代对象(iterable),但对象obj_list不是迭代器(iterator)。虽然对象obj_list是可迭代对象(iterable),但它却不能被迭代(遍历)。

from collections.abc import Iterable, Iterator

class MyList:
    def __init__(self,*args) -> None:
        self.data = list(args)

    def __iter__(self):
        print("__iter__被执行了...")
        return self
    
obj_list = MyList(2,4,6)
print(isinstance(obj_list, Iterable))  # 输出:True
print(isinstance(obj_list, Iterator))  # 输出:False

  (2)迭代器(iterator):在Python中,如果一个对象同时实现了__iter__方法和__next__方法,那它就是迭代器。也就是说,迭代器是一个实现了__iter____next__方法的对象。__iter__方法返回迭代器对象自身,而 __next__方法返回下一个元素。换句话说,迭代器是一个可以逐个返回元素的对象。如果在python的类中定义了__next____iter__方法,生成的实例对象可以通过for循环遍历来取,并且先调用__iter__方法,再调用__next__方法。代码如下:

from collections.abc import Iterable, Iterator

class MyList:
    def __init__(self,*args) -> None:
        self.data = list(args)

    def __iter__(self):
        print("__iter__被执行了...")
        return self
    def __next__(self):
        pass
obj_list = MyList(2,4,6)
print(isinstance(obj_list, Iterable))  # 输出:True
print(isinstance(obj_list, Iterator))  # 输出:True

由于我们在类中实现了__iter____next__方法,所以对象obj_list既是可迭代对象(iterable),又是一个迭代器(iterator)。由此可得出一个结论:迭代器(iterator)一定是可迭代对象(iterable)

  (3)内置函数iter()iter()函数的作用是将可迭代对象(iterable)转为一个迭代器(iterator)。下面以列表为例,示例代码如下:

from collections.abc import Iterable, Iterator
obj_list = [1,2,3,4]
obj_iterator = iter(obj_list) # 将可迭代对象列表转为迭代器
print(isinstance(obj_iterator, Iterable))  # 输出:True
print(isinstance(obj_iterator, Iterator))  # 输出:True

由上面代码可知:iter()函数可以将可迭代对象列表(list)转为迭代器。

  (4)内置函数next()next()函数的作用是从迭代器对象那里返回下一个值。如果我们使用内置函数next()对迭代器进行遍历,在这个过程中,是在调用迭代器的__next__方法。即:next()函数内部调用迭代器的__next__方法。

obj_list = [1,2,3,4]
obj_iterator = iter(obj_list) # 将可迭代对象列表转为迭代器
print(next(obj_iterator)) # 输出:1
print(next(obj_iterator)) # 输出:2
print(next(obj_iterator)) # 输出:3
print(next(obj_iterator)) # 输出:4
print(next(obj_iterator)) # 输出:StopIteration

由上面代码可知:最后一次取值会抛出StopIteration异常,表明迭代器里已经没有下一个值了。总之,遍历迭代器需要使用next()函数,每调用一次next(),就会返回一个值。当没有值可以返回时,就会引发StopIteration异常。

  (5)自定义迭代器,代码示例如下。

from collections.abc import Iterable,Iterator

class MyList:
    def __init__(self,*args) -> None:
        self.data = list(args)
        self.start = 0

    def __iter__(self):
        print("__iter__被执行了...")
        return self
    
    def __next__(self):
        print("__next__被执行了...")
        if self.start >= len(self.data):
        # raise StopIteration用于提前终止一个迭代器中的循环
            raise StopIteration
        item = self.data[self.start]
        self.start += 1
        return item
    
obj_list = MyList(2,4,6)
print(isinstance(obj_list,Iterable)) # 输出:True
print(isinstance(obj_list,Iterator)) # 输出:True

for i in obj_list:
    print(i)

输出:
True
True
__iter__被执行了...
__next__被执行了...
2
__next__被执行了...
4
__next__被执行了...
6
__next__被执行了...

  (6)总结:

  • .迭代器(iterator): 如果一个对象同时实现了__iter__方法和__next__方法,它就是迭代器;
  • 可迭代对象(iterable): 如果一个对象实现了__iter__方法,那么这个对象就是可迭代对象;
  • 两者之间的关系:迭代器(iterator)一定是可迭代对象(iterable),反之则不成立。可迭代对象的__iter__方法必须返回一个迭代器。

参考文章:一文看懂python的迭代器和可迭代对象

13. __getattr__方法、__setattr__方法、__delattr__方法

  (1)__getattr__方法:当我们访问对象中一个不存在的属性时,会抛出异常,提示我们该对象没有该属性。而这个异常就是__getattr__方法抛出的,其原因在于它是访问一个不存在的属性的最后落脚点。

class Student:
    def __init__(self,name,age,id) -> None:
        self.name = name
        self.age = age
        self.id = id
    def __getattr__(self,item):
        # 这里的item就是那个不存在的属性名
        print("__getattr__被执行...")
        print(item)
        return f"该对象不存在属性:{item}"
    
stu = Student("ZS",16,"001")
print(stu.name)  # name属性存在,可以访问
print(stu.gender)  # gender属性不存在,会调用__getattr__方法。

输出:
ZS
__getattr__被执行...
gender
该对象不存在属性:gender

  (2)__setattr__方法:在对一个属性设置值的时候,会调用到这个方法,每个设置值的方式都会进入这个方法。如下代码所示:在实例化对象的时候,会调用__init__方法进行初始化。在__init__方法中对属性age进行值的设置,此时也会调用__setattr__方法。我们还可以新建一个属性name,并对其赋值,此时会调用__setattr__方法。

class Student:
    def __init__(self,_age) -> None:
        self.age = _age

    def __setattr__(self,key, value):
        # 这里的key为属性名,value是该属性对应的值
        print("__setattr__被执行了...")
        if value == 16:
            print("这里的key和value来自__init__方法")
        object.__setattr__(self, key, value)
# object.__setattr__(self, key, value)、
# self.__dict__[key] = value、
# super().__setattr__(key,value),这三种方法等价,都可以使用
   
stu = Student(16)
print(stu.age)

stu.age = 30
print(stu.age)

stu.name = "ZS"
print(stu.name)

输出:
__setattr__被执行了...
这里的key和value来自__init__方法
16
__setattr__被执行了...
30
__setattr__被执行了...
ZS

注意:在重写__setattr__方法的时候千万不要重复调用,以防造成死循环。下面的代码就是一个死循环,在__init__方法中执行语句self.age = _age的时候会调用__setattr__方法。而__setattr__方法中又存在语句self.key = value,因此会继续调用__setattr__方法,造成死循环。

class Student:
    def __init__(self,_age) -> None:
        self.age = _age

    def __setattr__(self,key, value):
        self.key = value
   
stu = Student(16)

  (3)__delattr__方法:用于删除对象中的某个属性。

class Student:
    def __init__(self,name,age,id) -> None:
        self.name = name
        self.age = age
        self.id = id

    def __getattr__(self,item):
        print("__getattr__被执行了...")
        return f"该对象不存在属性:{item}"
    
    def __delattr__(self, item):
        print("__delattr__被执行了...")
        object.__delattr__(self,item)
    
stu = Student("ZS",16,"001")
del stu.id  # 会调用__delattr__方法
print(stu.id)

输出:
__delattr__被执行了...
__getattr__被执行了...
该对象不存在属性:id

14. __enter__方法和__exit__方法

  (1)with上下文管理器:关键字with使用最多的就是打开文件,然后进行读写操作。如下代码:

with open("data.txt","w+") as f_obj:
    f_obj.write("hello java")
    
# 上面的代码等价于下面的代码
f_obj = open("data.txt","w+")
f_obj.write("hello java)
f_obj.close()

使用with语句的时候不需要显式地去关闭文件资源,因为它会自动关闭。那么这个自动关闭是怎么实现的呢,这其实就是__enter____exit__魔法方法在起作用。
  (2)__enter____exit__方法的工作原理:我们使用with ... as ...来打开文件,首先进入__enter__方法,__enter__方法会返回一个对象,然后将该对象赋给关键字as后面的变量;当with ... as ...语句体结束时,会自动调用__exit__方法。

class OpenFile:
    def __init__(self,file_name="",mode="r"):
        self.__file = open(file_name, mode)

    def __enter__(self):
        print("__enter__被执行...")
        return self

    def write(self,ctx):
        print("writer方法被执行...")
        self.__file.write(ctx)
        print("writer方法执行结束...")

    def __exit__(self,exec_type,exec_value,exec_tb):
        print("__exit__方法被执行...")
        self.__file.close()

with OpenFile("demo.txt","w+") as f:
    f.write("hello world")


输出:
__enter__被执行...
writer方法被执行...
writer方法执行结束...
__exit__方法被执行...

  (3)__exit__方法中的三个参数:exec_type(异常类型),exec_value(异常对象),exec_tb(异常追踪信息),这些参数在异常处理的时候相当有用。如果with代码块正常执行完毕,这些参数都为None。通过下面代码来理解它是怎样工作的。

class Test:
    def __enter__(self):
        print("__enter__被执行...")
        return self

    def compute(self):
        print(1/0)

    def __exit__(self,exec_type,exec_value,exec_tb):
        print("__exit__方法被执行...")
        print(f"exec_type = {exec_type}")
        print(f"exec_value = {exec_value}")
        print(f"exec_tb = {exec_tb}")

with Test() as obj_test:
    obj_test.compute()

输出:
__enter__被执行...
__exit__方法被执行...
exec_type = <class 'ZeroDivisionError'>
exec_value = division by zero
exec_tb = <traceback object at 0x000001F61AB8F388>
Traceback (most recent call last):
  File "d:/code/day2/test.py", line 37, in <module>
    obj_test.compute()
  File "d:/code/day2/test.py", line 28, in compute
    print(1/0)
ZeroDivisionError: division by zero

with后面的代码块抛出任何异常时,__exit__方法都会被执行。与之关联的type,value和stack trace会传给__exit__方法的参数exec_type,exec_value,exec_tb。我们可以在__exit__方法中执行必要的清理操作(如关闭文件或释放资源)。

参考文章:Python----魔法函数__enter__/__exit__的用法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值