目录
1、isinstance(obj,cls)和issubclass(sub,super)
isinstance(obj,cls)检查是否obj是否是类 cls 的对象
2、__setattr__,__delattr__,__getattr__
一 反射
在python中,可以通过字符串的的形式来操作对象的属性。这种行为称之为python中的反射
它可以把字符串映射到实例的变量或者实例的方法然后可以去执行调用、修改等操作。
下面是反射的四个基本方法:
- getattr 获取指定字符串名称的对象属性 该函数返回的是属性的值或者方法的内存地址
- setattr 为对象设置一个对象 setattr(x,y,v) 注意第二个参数是字符串,第三个参数是要设置的属性的的值或者方法的形式
- hasattr 判断对象是否有对应的对象(字符串)hasattr(object,name) 存在返回True,不存在返回False。
- delattr 删除指定属性 delattr(x,y) 注意第二个参数是字符串,如果删除的不存在则会报错
使用范围:
- 对象和类
- 当前模块成员的反射 利用
sys.modules[__name__]
#sys.modules查看所有被导入的模块,以字典的形式存储
- 其他文件模块的反射 import进来的模块
二 内置函数
1、isinstance(obj,cls)和issubclass(sub,super)
isinstance(obj,cls)检查是否obj是否是类 cls 的对象
class Foo:
pass
obj = Foo()
print(isinstance(obj,Foo)) #输出结果 True
issubclass(sub, super)检查sub类是否是 super 类的派生类
class Foo(object):
pass
class Son(Foo):
pass
print(issubclass(Son,Foo)) #输出结果True
print(issubclass(Foo,object)) #输出结果True
2、__setattr__,__delattr__,__getattr__
__setattr__
_添加/修改属性会触发它的执行
__delattr__
_删除属性的时候会触发
__getattr__
只有在使用点调用属性且属性不存在的时候才会触发,调用存在的属性或者方法时不触发
class Foo():
def __init__(self,name):
self.name = name
def func(self):
print("func------")
def __delattr__(self, item):
print("执行__delattr__")
# del self.item #无限递归
self.__dict__.pop(item)
def __setattr__(self, key, value):
print("执行__setattr__")
# self.key=value #进入无限递归
self.__dict__[key]=value #应该使用它
p = Foo("jack") #实例化的过程中会增加属性,执行__setattr__,执行self.__dict__[key]=value语句,为name属性赋值
print(p.name) #
p.age = 18 #执行__setattr__方法,对象新增加属性
print(p.__dict__) #输出结果{'name': 'jack', 'age': 18}
del p.age #调用__delattr__方法,删除对象的age属性
print(p.__dict__) #输出结果{'name': 'jack'},此时已经不包含age属性
输出:
执行__setattr__
jack
执行__setattr__
{'name': 'jack', 'age': 18}
执行__delattr__
{'name': 'jack'}
class Foo():
def __init__(self,name):
self.name = name
def func(self):
print("func------")
def __getattr__(self, item):
print("执行__getattr__",item)
p = Foo("jack") #实例化的过程中会增加属性,
print(p.__dict__)
print(p.name)
print(p.age) #调用不存在的属性会执行__getattr__
输出:
{'name': 'jack'}
jack
执行__getattr__ age
None
3、__getattribute__
object.__getattribute__(self, name)
通过实例访问属性,无条件被调用
每次通过实例访问属性,都会经过
__getattribute__
函数。而当属性不存在时,仍然需要访问
__getattribute__
,不过接着要访问__getattr__
。这就好像是一个异常处理函数。 需要注意的是,当使用类访问不存在的变量是,不会经过__getattr__
函数。当属性存在时,经过
__getattribute__
函数,返回属性的值。
class Foo:
def __init__(self,x):
self.x = x
def __getattr__(self, item):
print("执行__getattr__")
def __getattribute__(self, item):
print("执行__getattribute__")
# raise AttributeError("不存在的异常")
f = Foo("nick")
print(f.x)
print(f.xxxx)#
输出:
执行__getattribute__
None
执行__getattribute__
None
note:这里本来调用f.xxxx后应该在__getattribute__中产生AttributeError,然后调用__getattr__,但在#这里__getattribute__被重写了,没有产生AttributeError,所以就没调用__getattr__
也就是说__getattr__的调用是通过__getattribute__中产生AttributeError异常
4、__format__
自定制格式化字符串
format_dic = {
"ymd":"{0.year}{0.month}{0.day}",
"dmy":"{0.day}:{0.month}:{0.year}",
"mdy":"{0.month}-{0.day}-{0.year}"
}
class Foo:
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day
def __format__(self, format_spec):
if not format_spec or format_spec not in format_dic:
format_spec = "ymd"
fmt = format_dic[format_spec]
return fmt.format(self)
f = Foo(2018,8,20)
print(format(f))
print(format(f,"dmy"))
print(format(f,"mdy"))
print("{:mdy}".format(f))
输出:
2018820
20:8:2018
8-20-2018
8-20-2018
5、__doc__
class Foo:
"""
我是文档注释
"""
pass
f = Foo()
print(f.__doc__) #输出结果 : 我是文档注释
6、__del__
析构方法,当对象在内存中被释放时,自动触发执行。即清除释放内存的方法。
这个是在回收实例化对象时触发执行的方法,删除对象的属性不会触发__del__
方法
使用场景:1,del obj 2 程序结束时,python解释器自动回收内存,执行了__del__
方法
典型的应用场景:
创建数据库类,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中
当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们定制__del__
,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源。
class Foo:
def __del__(self):
print("我执行了")
f = Foo()
f.file = open("test.txt") #打开文件,在操作系统中打开了一个文件,
# 拿到了文件操作符存在了内存中
del f.file #删除了内存中的文件操作符,但是并未关闭操作系统中的文件,
# 这时需要在__del__中加入定制f.file.close(),关闭操作系统的文件
print("-----1")
#最后程序结束python解释器自动执行内存回收,执行了__del__方法
三 描述符(descriptor)
(__get__,__set__,__delete__
)
python中的描述符可以用来定义触发自动执行的代码,它像是一个对象属性操作(访问、赋值、删除)的代理类一样。前面介绍过的property是描述符的一种。
大致流程是这样的:
- 定义一个描述符类D,其内包含一个或多个
__get__()
、__set__()
、__delete__()
方法 - 将描述符类D的实例对象d赋值给另一个要代理的类中某个属性attr,即
attr = D()
- 之后访问、赋值、删除attr属性,将会自动触发描述符类中的
__get__()
、__set__()
、__delete__()
方法
简言之,就是创建一个描述符类,它的实例对象作为另一个类的属性。
要定义描述符类很简单,只要某个类中包含了下面一个或多个方法,就算是满足描述符协议,就是描述符类,就可以作为属性操作的代理器。
还需注意的是不要把__delete__
和__del__
搞混了,前者是实现描述符协议的一个方法,后者是对象销毁函数(也常称为析构函数)。
先不管这几个方法中的参数,看一个示例先:
class Descriptor():
def __get__(self, instance, owner):
print("self: %s\ninstance: %s\nowner: %s" % (self, instance, owner))
class S:
# 描述符的示例对象作为S的属性
attr = Descriptor()
s1 = S()
s1.attr # 访问对象属性
print("-" * 30)
S.attr # 访问类属性
结果:
self: <__main__.Descriptor object at 0x030C02D0>
instance: <__main__.S object at 0x030C0AB0>
owner: <class '__main__.S'>
------------------------------
self: <__main__.Descriptor object at 0x030C02D0>
instance: None
owner: <class '__main__.S'>
这里解释下__get__(self, instance, owner)
中的三个参数:
- self:描述符对象自身,也就是被代理类S中的属性attr
- instance:被代理类的实例对象。所以访问类属性(class.attr)时为None
- owner:将描述符对象附加到哪个类上,其实是instance所属的类,也就是type(instance)
再解释下这里相关的几个角色:
Descriptor
:是描述符类,也是代理者S
:是另一个类,是托管类、客户类,也就是参数中的ownerattr = Descriptor()
:是描述符的实例对象,attr是托管类的属性,也就参数中的selfs1
:是托管类实例对象,也就是参数中的instance
1、描述符本质
就是一个新式类,在这个新式类中,至少实现了__get__()
,__set__()
,__delete__()
中的一个,这也被称为描述符协议。
__get__()
:调用一个属性时,触发
__set__()
:为一个属性赋值时,触发
__delete__()
:采用del删除属性时,触发
2、描述符的作用
是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
描述符是在另外一个类的类属性进行定义的,描述符在一个类的类属性__dict__
字典里
如果class定义了它,则这个class就可以称为descriptor。owner是所有者的类,instance是访问descriptor的实例,如果不是通过实例访问,而是通过类访问的话,instance则为None。(descriptor的实例自己访问自己是不会触发__get__,而会触发__call__,只有descriptor作为其它类的属性才有意义。)
当然关于描述符,我们还需要知道,如果一个类仅仅实现了__get__()
方法,那么这个类被称为非数据描述符;如果一个类实现在__get__()
并且还实现在 __set__()
和__del__()
中的一个,这个类就被称为数据描述符
3、举例说明
当定义了一个类后,可以访问、赋值、删除它的属性,这些操作也同样适用于它的实例对象。
from weakref import WeakKeyDictionary
class Score():
""" score should in [0,100] """
def __init__(self):
self.score = WeakKeyDictionary()
#self.score = {}
def __get__(self, instance, owner):
return self.score[instance]
def __set__(self, instance, value):
if 0 <= value <= 100:
self.score[instance] = value
else:
raise ValueError("score not in [0,100]")
class Student():
# 托管属性定义在类级别上
score1 = Score()
score2 = Score()
score3 = Score()
def __init__(self, stuid, name, score1, score2, score3):
self.stuid = stuid
self.name = name
self.score1 = score1
self.score2 = score2
self.score3 = score3
def returnMe(self):
print(self)
return "%s, %s, %i, %i, %i" % (
self.stuid,
self.name,
self.score1,
self.score2,
self.score3)
if __name__=='__main__':
stu = Student("20101120", "malong", 67, 77, 88)
print(stu.returnMe())
# stu.score1 = -23
print(stu)
print(type(stu))
输出:
<__main__.Student object at 0x00000285B03FB390>
20101120, malong, 67, 77, 88
<__main__.Student object at 0x00000285B03FB390>
<class '__main__.Student'>
note:定义了stu以后,Student里的self就是类的实例stu 所以无论是self 还是stu 都会触发描述符类中的
__get__()
、__set__()
、__delete__()
方法
class Foo:
def __get__(self, instance, owner):
print("执行了__get__")
def __set__(self, instance, value):
print("执行了__set__")
def __delete__(self, instance):
print("执行了__delete__")
class Bar:
x = Foo()
def __init__(self,name):
self.name = name
b = Bar("nick")
b.x #调用执行描述符里的__get__方法
print(b.x) #
b.x = 1 # 调用执行描述符里的__set__方法
print(b.__dict__)
del b.x #调用执行描述符里的__delete__方法
print(b.__dict__)
输出:
执行了__get__
执行了__get__
None
执行了__set__
{'name': 'nick'}
执行了__delete__
{'name': 'nick'}
#描述符Str
class Str:
def __get__(self, instance, owner):
print('Str调用')
def __set__(self, instance, value):
print('Str设置...')
def __delete__(self, instance):
print('Str删除...')
#描述符Int
class Int:
def __get__(self, instance, owner):
print('Int调用')
def __set__(self, instance, value):
print('Int设置...')
def __delete__(self, instance):
print('Int删除...')
class People:
name=Str()
age=Int()
def __init__(self,name,age): #name被Str类代理,age被Int类代理,
self.name=name
self.age=age
#何地?:定义成另外一个类的类属性
#何时?:且看下列演示
p1=People('alex',18)
#描述符Str的使用
p1.name
p1.name='egon'
del p1.name
#描述符Int的使用
p1.age
p1.age=18
del p1.age
#我们来瞅瞅到底发生了什么
print("__p1.__dict__",p1.__dict__)
print(People.__dict__)
#补充
print(type(p1) == People) #type(obj)其实是查看obj是由哪个类实例化来的
print(type(p1).__dict__ == People.__dict__)
输出:
Str设置...
Int设置...
Str调用
Str设置...
Str删除...
Int调用
Int设置...
Int删除...
__p1.__dict__ {}
{'__module__': '__main__', 'name': <__main__.Str object at 0x021C6850>, 'age': <__main__.Int object at 0x021C6870>, '__init__': <function People.__init__ at 0x021C5DB0>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
True
True
note:当描述符和构造函数同名时,调用描述符
4、描述符分两种
(1) 数据描述符:至少实现了__get__()
和__set__()
(2) 非数据描述符:没有实现__set__()
注意:非数据描述符一般是只有__get__,如果保留__delete__执行会报错。
5、 注意事项:
(1)描述符本身应该定义成新式类,被代理的类也应该是新式类(python3中全部是新式类)
(2)必须把描述符定义成另外一个类的类属性,不能为定义到构造函数中,
(3)要严格遵循该优先级,优先级由高到底分别是
a.类属性b.数据描述符c.实例属性d.非数据描述符e.找不到的属性触发__getattr__()
class Foo:
def __get__(self, instance, owner):
print("执行了__get__")
def __set__(self, instance, value):
print("执行了__set__")
def __delete__(self, instance):
print("执行了__delete__")
class People1:
name = Foo()
def __init__(self,name):
self.name = name
p = People1("nick") #执行__init__后执行__set__
print(People1.__dict__) #{'__module__': '__main__', 'name': <__main__.Foo2 object at 0x00000157D73AD208>, '__init__': <function People1.__init__ at 0x00000157D72ED378>, '__dict__': <attribute '__dict__' of 'People1' objects>, '__weakref__': <attribute '__weakref__' of 'People1' objects>, '__doc__': None}
print(People1.name) # 执行了__get__ None
print(p.__dict__) #{}
People1.name = "NICK" # 调用执行了描述符的__set__方法,这一步类属性由之前的描述符被定义成另外一个字符串,
# 所以下面再次调用就无法再次使用描述符了
print(People1.name) #NICK
print(p.__dict__) #{}
print(People1.__dict__) #{'__module__': '__main__', 'name': 'NICK', '__init__': <function People1.__init__ at 0x00000157D72ED378>, '__dict__': <attribute '__dict__' of 'People1' objects>, '__weakref__': <attribute '__weakref__' of 'People1' objects>, '__doc__': None}
NOTE:可以得出结论,类属性的优先级大于数据描述符
class Foo:
def __get__(self, instance, owner):
print("执行了__get__")
def __set__(self, instance, value):
print("执行了__set__")
def __delete__(self, instance):
print("执行了__delete__")
class People:
name = Foo()
def __init__(self,name):
self.name = name
p = People("nick") #实例化对象,调用数据描述符的__set__,
# 但是由于描述符的__set__只是执行了打印操作,什么都没做,所以p对象的__dict__什么都没有
p.name = "nicholas"
print(p.__dict__) #输出的结果为空
NOTE:因此可以得出结论,数据描述符的优先级大于实例属性(字典操作)
class Foo(object):
def __init__(self):
pass
def __get__(self, instance, owner):
print("执行了__get__")
class People(object):
name = Foo("x")
def __init__(self,name,age):
self.name = name
self.age = age
p = People("nick",18) #实例化对象,这里由于是非数据描述符,优先级低于实例属性,
# 所以这里直接设置了实例属性,而不再调用描述符
print(p.name) #打印直接输出实例属性
print(p.__dict__)
#输出的结果:{'name': 'nick', 'age': 18}
NOTE:因此可以得出结论,实例属性的优先级大于非数据描述符
class Foo(object):
def __init__(self,name2):
self.name2 = name2
def __get__(self, instance, owner):
print("执行了__get__")
class People(object):
name = Foo("x")
def __init__(self,name,age):
self.name = name
self.age = age
def __getattr__(self, item):
print("__getattr__")
p = People("nick",18) #实例化对象,这里由于是非数据描述符,优先级低于实例属性,
# 所以这里直接设置了实例属性,而不再调用描述符
print(p.name)
print(p.sex) #调用不存在的属性执行了__getattr__
print(p.__dict__)
#输出的结果:{'name': 'nick', 'age': 18}
6、描述符的应用
class Type:
def __init__(self,key,expect_type):
self.key = key
self.expect_type = expect_type
def __get__(self, instance, owner):
print("执行__get__方法")
print(self) #这里的self就是type类的对象
print(instance) #这里的instance就是传入的People类的对象
print("执行__get__方法")
return instance.__dict__[self.key] #通过instance的字典获取对象的属性值
def __set__(self, instance, value):
print("执行__set__方法")
instance.__dict__[self.key] = value #instance是另一个类的对象,这里要设置对象的属性字典
def __delete__(self, instance):
print("执行__delete__方法")
instance.__dict__.pop(self.key) #删除对象的属性
class People:
name = Type("name",str)
age = Type("age",int)
def __init__(self,name,age):
self.name = name
self.age = age
p1 = People("nick",18) #调用2次描述符,对对象的字典进行设置
print(p1.name) #通过数据描述符获取对象的属性值
print(p1.__dict__)
p1.age = 20 #调用描述符对对象进行设置
print(p1.__dict__)
输出:
执行__set__方法
执行__set__方法
执行__get__方法
<__main__.Type object at 0x004CB4F0>
<__main__.People object at 0x02106DF0>
执行__get__方法
nick
{'name': 'nick', 'age': 18}
执行__set__方法
{'name': 'nick', 'age': 20}
class Type:
def __init__(self,key,expect_type):
self.key = key
self.expect_type = expect_type
def __get__(self, instance, owner):
print("执行__get__方法")
print(self) #这里的self就是type类的对象
print(instance) #这里的instance就是传入的People类的对象
print("执行__get__方法")
return instance.__dict__[self.key] #通过instance的字典获取对象的属性值
def __set__(self, instance, value):
print("执行__set__方法")
if not isinstance(value,self.expect_type):
print("您输入的%s不是%s"%(self.key,self.expect_type))
raise TypeError
instance.__dict__[self.key] = value #instance是另一个类的对象,这里要设置对象的属性字典
def __delete__(self, instance):
print("执行__delete__方法")
instance.__dict__.pop(self.key) #删除对象的属性
class People:
name = Type("name",str)
age = Type("age",int)
def __init__(self,name,age):
self.name = name
self.age = age
p1 = People("nick",18) #调用2次描述符,对对象的字典进行设置
print(p1.name) #通过数据描述符获取对象的属性值
print(p1.__dict__)
p1.age = 20 #调用描述符对对象进行设置
print(p1.__dict__)
# p1.name = 11 #通过描述符的if not isinstance(value,self.expect_type)判断属性的类型
# p2 = People(88,18) #通过描述符的if not isinstance(value,self.expect_type)判断属性的类型
四 二次加工标准类型
包装
python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工),简单来说就是对以前的类的各种内置方法进行改写,继承原有功能,然后增加功能
class LIST(list): #继承list所有的属性
def append(self, object):
if type(object) is not str: #这里对append方法进行二次处理,增加了新的功能
print("%s 必须是字符串"%object)
super().append(object)
#list.append(self,object) 与上面一句作用相同,都是调用父类list的append方法
@property #使用property使得mid方法更像对象的数据属性,可以直接获得返回值
def mid(self): #获得列表中间的元素的值
index = len(self)
mid_index = index//2 #这里是取除以2的整数商部分,向下取整
return self[mid_index]
la = LIST("hellonick") #调用list的方法将字符串转为列表
print(la,type(la)) #输出结果 ['h', 'e', 'l', 'l', 'o', 'n', 'i', 'c', 'k'] <class '__main__.LIST'>
# 这里的la是类LIST的一个对象
print(la.mid) #输出结果 o
lb = list("hellonick")
print(lb,type(lb)) #输出结果['h', 'e', 'l', 'l', 'o', 'n', 'i', 'c', 'k'] <class 'list'>,
# 这里直接显示lb是列表类型,也是list的一个对象
class LIST(list): #继承list所有的属性
def __init__(self,item,tag=False):
super().__init__(item)
self.tag = tag
def append(self, object):
if type(object) is not str: #这里对append方法进行二次处理,增加了新的功能
print("%s 必须是字符串"%object)
super().append(object)
#list.append(self,object) 与上面一句作用相同,都是调用父类list的append方法
def clear(self):
if not self.tag: #改写原列表方法clear,增加权限限制
raise PermissionError
super().clear()
la = LIST("hellonick") #调用list的方法将字符串转为列表,输出结果 ['h', 'e', 'l', 'l', 'o', 'n', 'i', 'c', 'k']
print(la) #['h', 'e', 'l', 'l', 'o', 'n', 'i', 'c', 'k']
print(la.tag) #False
# la.clear() #会报错
lb = LIST([2,3,4,5,6,7])
print(lb) #[2, 3, 4, 5, 6, 7]
# lb.clear() #会报错
print(lb.tag) #False
lc = LIST([5,3,4,5,7],)
lc.tag = True
lc.clear()
print(lc) #输出结果为 []
授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
简单来说就是更新的功能用自己定义类的方法,没有更新的功能用父类的方法。自己定义了一个方法,与父类同名,那么就用自己的定义的方法,其他的方法继续用父类的方法。授权也是一种包装,但授权的实现不是通过继承,而是使用__getattr__
方法。
实现授权的关键点就是覆盖__getattr__
方法
class FileHandle:
def __init__(self,file,mode,encoding):
self.file = open(file,mode,encoding=encoding)
def __getattr__(self, item):
return getattr(self.file,item)
#这里调用属性的时候自动执行__getattr__方法,执行之后用getattr方法返回原属于open的方法
def read(self):
print("读取文件中。。。")
#如果有同名的方法那么就用自己定义的方法,不用__getattr__获得的方法,
# 因为__getattr__只有在使用点调用属性且属性不存在的时候才会触发
f = FileHandle("test.txt","w+","utf-8")
f.write("aaaaa")
f.seek(0)
data = f.read() #这里是执行了FileHandle的自己定义的read()方法,所以输出结果是None
print(data)
note: 利用.调用__getattr__函数时,要先判断类中是否有这个函数,如果有,则调用类中的函数,如果没有才调用__getattr__
import time
class FileHandle:
def __init__(self,file,mode,encoding):
self.file = open(file,mode,encoding=encoding)
def __getattr__(self, item):
return getattr(self.file,item)
#这里调用属性的时候自动执行__getattr__方法,执行之后用getattr方法返回原属于open的方法
def write(self,msg):
self.file.write("%s %s"%(time.strftime("%Y-%m-%d %X"),msg)) #调用self.file中open的方法
#为每次写入增加时间
f = FileHandle("test.txt","w+","utf-8")
f.write("aaaaa")
f.seek(0)
data = f.read()
print(data)
五 __enter__
和__exit__
打开文件操作用 with open() as f操作,这叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__
和__exit__
方法。
__enter__(self)
:当with开始运行的时候触发此方法的运行
__exit__(self, exc_type, exc_val, exc_tb)
:当with运行结束之后触发此方法的运行
exc_type如果抛出异常,这里获取异常的类型
exc_val如果抛出异常,这里显示异常内容
exc_tb如果抛出异常,这里显示所在位置
用途或者说好处:
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__
中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
class OPEN:
def __init__(self,name):
self.name = name
def __enter__(self):
print("执行__enter__")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("执行__exit__")
print(exc_type)
print(exc_val)
print(exc_tb)
print("执行__exit__2222")
with OPEN("a.txt") as f:
print(f) #执行打印__enter__内置方法,同时打印内置方法返回的结果
#with 语句结束时执行__exit__方法,没有错误则打印None,有错误则打印错误的信息
print("上下文管理协议")