Python之路(第二十七篇) 面向对象进阶:内置方法描述符
Posted Nicholas--Altshuler
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python之路(第二十七篇) 面向对象进阶:内置方法描述符相关的知识,希望对你有一定的参考价值。
对象后面加括号,触发执行类下面的__call__
方法。
创建对象时,对象 = 类名() ;而对于 __call__
方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo: ? def __call__(self, *args, **kwargs): print("我执行啦") ? f = Foo() f() #对象加括号调用执行类下的__call__方法 #输出结果 我执行啦
二、__next__
和__iter__
实现迭代器协议
迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退)
可迭代对象执行obj.__iter__()
得到的结果就是迭代器对象。
在类中,如果有__iter__
和__next__
内置方法,那么就构成了迭代器。
例子
class Foo: ? def __init__(self,n): self.n = n ? def __iter__(self): return self #实例本身就是迭代对象,故返回自己 ? def __next__(self): if self.n >10: raise StopIteration #如果超过10就报StopIteration 错误 self.n = self.n + 1 return self.n ? f = Foo(7) for i in f: #for循环自动调用__next__方法,实现了迭代取值 print(i)
例子2
输出100内的斐契那波数列
class F: ? def __init__(self): self.a = 0 self.b = 1 ? def __iter__(self): return self ? def __next__(self): self.a ,self.b = self.b , self.a + self.b if self.a > 100: raise StopIteration return self.a ? f = F() for i in f: print(i)
三、描述符(__get__,__set__,__delete__
)
描述符(descriptor):
1、描述符本质
就是一个新式类,在这个新式类中,至少实现了__get__()
,__set__()
,__delete__()
中的一个,这也被称为描述符协议。__get__()
:调用一个属性时,触发__set__()
:为一个属性赋值时,触发__delete__()
:采用del删除属性时,触发
2、描述符的作用
是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
描述符是在另外一个类的类属性进行定义的,描述符在一个类的类属性__dict__
字典里
例子1
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‘}
例子2
#描述符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
3、描述符分两种
(1) 数据描述符:至少实现了__get__()
和__set__()
? class Foo: def __set__(self, instance, value): print(‘set‘) def __get__(self, instance, owner): print(‘get‘)
(2) 非数据描述符:没有实现__set__()
class Foo: def __get__(self, instance, owner): print(‘get‘)
注意:非数据描述符一般是只有__get__,如果保留__delete__执行会报错。
4、 注意事项:
(1)描述符本身应该定义成新式类,被代理的类也应该是新式类(python3中全部是新式类)
(2)必须把描述符定义成另外一个类的类属性,不能为定义到构造函数中,
(3)要严格遵循该优先级,优先级由高到底分别是
a.类属性b.数据描述符c.实例属性d.非数据描述符e.找不到的属性触发__getattr__()
例子1
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") People.name = "nick" #调用执行了描述符的__set__方法,这一步类属性由之前的描述符被定义成另外一个字符串, # 所以下面再次调用就无法再次使用描述符了 People.name ? #可以得出结论,类属性的优先级大于数据描述符
例子2
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__) #输出的结果为空 ? #因此可以得出结论,数据描述符的优先级大于实例属性(字典操作)
例子3
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} ? #因此可以得出结论,实例属性的优先级大于非数据描述符
例子4
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}
5、描述符的应用
例子1
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}
?
例子2
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)判断属性的类型
四、__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("上下文管理协议")
以上是关于Python之路(第二十七篇) 面向对象进阶:内置方法描述符的主要内容,如果未能解决你的问题,请参考以下文章