描述符

描述符

描述符也是面向进阶的一种,由于它的涉及比较广,所以单独讲。

一、描述符

描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议。

描述符的作用是用来代理另外一个类的属性,必须把描述符定义成一个类的类属性,不能定义到构造函数中。

描述符分为两种,一种是数据描述符:至少实现了__get__()和__set__();另一种是非数据描述符:没有实现__set__()。

1.描述符格式

class Foo: #在python3中任何类都是新式类,它只要实现了三种方法之一,那么这个类就被称作一个描述符def __get__(self, instance, owner):  #调用一个属性时触发passdef __set__(self, instance, value):   #为一个属性赋值时触发passdef __delete__(self, instance):   #采用del删除属性时触发pass

2.描述符的使用

描述符该如何使用呢?具体代码如下:

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,n):  self.x = nb1 = Bar(10)
b1.x = 11111
b1.x
del b1.x

执行代码结果为:

我是set方法
我是set方法
我是get方法
我是delete方法

可以看出在实例化时也会触发__set__方法,因为在类Bar的初始化函数中self.x是被描述符代理的属性。那么对象b1中的属性字典中到底存不存在x这个值呢?具体代码如下所示:

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,n):  self.x = nb1 = Bar(10)
print(b1.__dict__)
b1.x = 11111
print(b1.__dict__)

执行结果为:

我是set方法
{}
我是set方法
{}

这是什么情况?无论是实例化操作还是赋值操作,在对象b1的属性字典中仍然为空。正是因为描述符的关系,它相当于把被描述符的类的调用属性操作、赋值操作、删除操作都赋予另一个类来实现,跟本身类并没有关系,当然这关系到优先级的问题。

3.描述符的优先级

我们要严格遵守优先级:类属性>数据描述符>实例属性>非数据描述符>找不到的属性触发__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 Bar:x = Foo()Bar.x=111   
print(Bar.x)   #结果为:111

上述代码打印结果仍为111,并没有执行数据描述符,是因为Bar类调用了本来要被描述符代理的属性x进行了修改。所有类属性的优先级比数据描述符高。

(2)数据描述符>实例属性的分析代码如下:

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()b1 = Bar()
b1.x  #结果为:我是get方法
b1.x = 111   #结果为:我是set方法

上述代码Foo类被定义成Bar类的类属性,即对Bar类进行实例化,实例属性只执行数据描述符的方法。说明了数据描述符的优先级高于实例属性。

(3)实例属性>非数据描述符的分析代码如下:

class Foo():def __get__(self, instance, owner):print('我是get方法')
class Bar:x = Foo()b1 = Bar()
b1.x = 111   
print(b1.__dict__)    #结果为:{'x': 111}

上述代码可以看出实例给自己属性进行赋值操作,可以在实例的属性字典中找到,这说明了实例属性的优先级高于非数据描述符。

(4)非数据描述符>找不到的属性触发__getattr__()的分析代码如下:

class Foo():def __get__(self, instance, owner):print('我是get方法')
class Bar:x = Foo()def __getattr__(self, item):print('我是getattr方法')
b1 = Bar()
b1.x    #结果为:我是get方法
b1.name   #结果为:我是getattr方法

上述代码可以看出找得到时触发__get__方法,找不到就会触发__getattr__方法。说明了非数据描述符的优先级高于找不到的属性触发__getattr__()。

4.描述符的应用

描述符可以应用到哪些场合呢?我们就来举个例子,通过描述符机制来实现参数的赋值类型限制。即:

class Typed:def __init__(self,key,expected_type):self.key = keyself.expected_type = expected_typedef __get__(self, instance, owner):print('get方法')return instance.__dict__[self.key]def __set__(self, instance, value):print('set方法')if not isinstance(value,self.expected_type):raise TypeError('你传入的类型不是%s' %self.expected_type)instance.__dict__[self.key] = valuedef __delete__(self, instance):print('delete方法')instance.__dict__.pop(self.key)
class People:name = Typed('name',str)age = Typed('age',int)salary = Typed('salary',float)def __init__(self,name,age,salary):self.name = nameself.age = ageself.salary = salaryp1 = People('alex',18,6666.66)
p2 = People(250,26,4568.55)  #不符合赋值类型,抛出异常

上述代码对象p1是满足参数的赋值类型,所以会触发三次__set__方法。而对象p2不符合赋值类型,就会抛出异常。

5.propetry

一个静态属性property本质就是实现了__get__,__set__,__delete__三种方法。

propetry有两种用法,第一种即:

class Foo:@property   #静态属性def AAA(self):print('get的时候运行')@AAA.setterdef AAA(self,value):print('set的时候运行',value)@AAA.deleterdef AAA(self):print('delete的时候运行')
f1 = Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA

执行结果为:

get的时候运行
set的时候运行 aaa
delete的时候运行

上述代码中只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter。

第二种用法的代码实现如下:

class Foo:def get_AAA(self):print('get的时候运行')def set_AAA(self,value):print('set的时候运行',value)def del_AAA(self):print('delete的时候运行')AAA = property(get_AAA,set_AAA,del_AAA)  #内置property三个参数与get,set,delete一一对应
f1 = Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA

执行结果为:

get的时候运行
set的时候运行 aaa
delete的时候运行

上述两种用法的结果都是一样的。

我们也可以利用描述符自定制property,实现延时计算。我们先对一段代码进行分析,来看看我们利用描述符自定制property该如何实现相同的目的。即:

class Room:def __init__(self,name,width,length):self.name = nameself.width = widthself.length = length@property   #area=property(area)def area(self):return self.width*self.length
r1 = Room('别墅',15,16)
print(r1.area)   #结果为:240

在上述代码中@property相当于实现了area=property(area),而property它是一个类,这是不是相当于property类定义成Room类的类属性,这不就是描述符的性质吗?它既然是描述符,那么它是数据描述符还是非数据描述符呢?从结果它打印实例属性可以看出,它是非数据描述符,因为实例属性的优先级>非数据描述符。假如我们自定制property,不采用内置的静态属性property,该如何实现上述代码,即:

class Lazyproperty:def __init__(self,func):self.func = funcdef __get__(self, instance, owner):return self.func(instance)  #instance实例本身
class Room:def __init__(self,name,width,length):self.name = nameself.width = widthself.length = length@Lazyproperty   #area=Lazyproperty(area)def area(self):return self.width*self.length
r1 = Room('别墅',15,16)
print(r1.area)   #结果为:240

上述代码中的@Lazyproperty是我自定制的,当然Lazyproperty类中我们只能定义成非数据描述符,否则不会执行area方法。

我们继续利用自定制property来实现延时计算功能:

class Lazyproperty:def __init__(self,func):self.func = funcdef __get__(self, instance, owner):print('get')if instance is None:     #被类调用必须写,否则报错,因为类没有实例return selfres = self.func(instance)  #instance实例本身setattr(instance,self.func.__name__,res)  #缓存return res
class Room:def __init__(self,name,width,length):self.name = nameself.width = widthself.length = length@Lazyproperty   #area=Lazyproperty(area)def area(self):return self.width*self.length
r1 = Room('别墅',15,16)
print(r1.area)  #从字典里先找,因为实例属性>非数据描述符,没有再去类的中找,然后出发了area的__get__方法
print(r1.area)  #先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算

上述代码实现了延时计算功能,这样这不会每次都打印get的操作。描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性。

 6.类的装饰器

类的装饰器与函数的装饰器性质是一样的,类的装饰器分为无参装饰器和有参装饰器。

我们先来定义一个无参的装饰器,即:

def deco(func):print('============')func.x = 1func.y = 2return func
@deco    #相当于Foo = deco(Foo)
class Foo:pass
print(Foo.__dict__)

执行结果为:

============
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 1, 'y': 2}

上述代码当程序遇到@deco就马上执行了Foo=deco(Foo),可以在结果可以看出Foo类中的属性字典中有键值对x与y。

下面我们来介绍有参的装饰器该如何定义:

def Typed(**kwargs):def deco(obj):for key,val in kwargs.items():setattr(obj,key,val)return objreturn deco@Typed(x=1,y=2,z=3)   #1.Typed(x=1,y=2,z=3)--->deco   2.@deco----->Foo=deco(Foo)
class Foo:pass
print(Foo.__dict__)@Typed(name='alex')  #@deco---->Bar=deco(Bar)
class Bar:pass
print(Bar.name)

执行的结果为:

{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 1, 'y': 2, 'z': 3}
alex

上述代码定义了有参的装饰器,函数Typed是用来接收所有参数的。

下面我们利用装饰器的应用以及描述符来实现参数的赋值类型限制,即:

class Typed:def __init__(self,key,expected_type):self.key = keyself.expected_type = expected_typedef __get__(self, instance, owner):print('get方法')return instance.__dict__[self.key]def __set__(self, instance, value):print('set方法')if not isinstance(value,self.expected_type):raise TypeError('你传入的类型不是%s' %self.expected_type)instance.__dict__[self.key] = valuedef __delete__(self, instance):print('delete方法')instance.__dict__.pop(self.key)def deco(**kwargs):def wrapper(obj):for key,val in kwargs.items():setattr(obj,key,Typed(key,val))   #Typed(key,val)描述符return objreturn wrapper@deco(name = str,age = int,salary = float)
class People:def __init__(self,name,age,salary):self.name = nameself.age = ageself.salary = salaryp1 = People('alex',20,6666.66)

实例化时触发了三次__set__方法。在描述符里也规定了参数的赋值类型。

转载于:https://www.cnblogs.com/lzc69/p/11316469.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/363213.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++迭代器简介

迭代器是一种检查容器内元素并遍历元素的数据类型。 标准库为每一种标准容器&#xff08;包括 vector&#xff09;定义了一种迭代器类型。迭代器类型提供了比下标操作更通用化的方法&#xff1a;所有的标准库容器都定义了相应的迭代器类型&#xff0c;而只有少数的容器支持下标…

编写干净的测试-被认为有害的新内容

很难为干净的代码找到一个好的定义&#xff0c;因为我们每个人都有自己的单词clean的定义。 但是&#xff0c;有一个似乎是通用的定义&#xff1a; 干净的代码易于阅读。 这可能会让您感到有些惊讶&#xff0c;但是我认为该定义也适用于测试代码。 使测试尽可能具有可读性是我…

所谓经济现象

随想一下&#xff0c;先举出一些基本认知&#xff0c;后面大家会看到这些认知之间的牵扯&#xff0c;已经人在做决策的时候是如何分配权重的&#xff0c;进而产生后续和当今的局面。经济学一点都不难&#xff0c;只是现在人学的是技术上的操作&#xff0c;而不学背后的运作逻辑…

hdu-1277--字典树坑题

hdu-1227 字典树&#xff0c;坑题&#xff01;&#xff01;当字典树练手 Problem Description 我们大家经常用google检索信息&#xff0c;但是检索信息的程序是很困难编写的&#xff1b;现在请你编写一个简单的全文检索程序。 问题的描述是这样的&#xff1a;给定一个信息流文件…

Do not mutate vuex store state outside mutation handlers.

组件代码&#xff1a; selectItem(item,index) {this.selectPlay({list: this.songs,index}) }, ...mapActions([selectPlay ]) mutation 代码&#xff1a; [types.SET_PLAYLIST](state, list) {// 1、state.playlist JSON.parse(JSON.stringify(list))// 2、state.playlist …

硅谷企業面臨新的反壟斷枷鎖

硅谷的公司正面臨著加強反壟斷審查的新階段﹐這是對奧巴馬政府加強執法和持續不斷的海外壓力所做出的反應。 對在其行業中佔據主導地位的企業採取更嚴格的立場可能會考驗這些科技業巨頭在布什政府時期採取的政府關係策略。Associated Press奧巴馬政府任命的司法部負責反壟斷執法…

Spring Java配置:会话超时

当您可以使用基于Java的配置开发Spring应用程序时&#xff0c;我们生活在一个美好的时光。 不再有多余的XML代码&#xff0c;只有纯Java代码。 在本文中&#xff0c;我想讨论一个关于Spring应用程序中会话管理的热门话题。 更确切地说&#xff0c;我将以Java配置样式讨论会话超…

怎样去掉警告 log4j:WARN No appenders could be found for logger

最近在作项目的时候&#xff0c;用到了 HttpClient&#xff0c;用它向 HTTP server 发送请求并处理返回的页面数据。 我的应用类间接调用 HttpClient。在运行的时候&#xff0c;程序打印出了如下警告信息&#xff1a; log4j:WARN No appenders could be found for logger log…

这首歌【好听】到哭出来

转载于:https://www.cnblogs.com/Agnel-Cynthia/p/10662886.html

Raect Router 4 的使用 (1)

本文来自于官方文档&#xff0c;属于意译而非直译 基本组件 React Router 有三种类型的组件&#xff0c;分别是&#xff1a;react-router、react-router-dom、react-router-native 你在web 程序中使用了路由组件&#xff0c;那你就应该引入 react-router-dom&#xff1a; im…

分而治之思想

当一个问题的规模很大时&#xff0c;直接求解往往比较困难。对于这类问题&#xff0c;很大一部分是可以采取分而治之的思想来处理的。 分治法是把问题划分成多个子问题来进行处理。这些子问题&#xff0c;在结构上跟原来的问题一样&#xff0c;但是规模比原来的问题要小。如果得…

在Java 8中使用Stream API解析文件

Java 8中到处都有流。只需四处看看&#xff0c;可以肯定地找到它们。 它也适用于java.io.BufferedReader 。 使用Stream API在Java 8中解析文件非常容易。 我有一个要读取的CSV文件。 下面的例子&#xff1a; username;visited jdoe;10 kolorobot;4我的阅读器的一项合同是提供…

诡异的DateTime.TryParseExact方法

老赵在介绍Routing扩展的WebCast中出了点“小状况”&#xff0c;即将DateTime.ToString(“yyyy-MM-dd”)修改为DateTime.ToString(“yyyy/MM/dd”)后&#xff0c;页面中仍然显示为yyyy-MM-dd样式的日期格式。相信看过WebCast的同学都还记得吧。我不解&#xff0c;将老赵代码中D…

【手撸一个ORM】第一步、实体约定和描述

一、约定 数据实体必须实现 IEntity 接口&#xff0c;该接口定义了一个int类型的Id属性&#xff0c;既每个实体必须有一个名称为Id的自增主键。 若数据表的主键列名称不是Id&#xff0c;可以通过 [MyKey("主键列名")] 对该属性进行描述 namespace MyOrm.Commons {pub…

this指向问题(2)

4、显示绑定 指的是apply、bind、call &#xff08;1&#xff09;、apply 和 call 相同点&#xff1a; <1> 这两个方法的用途是在特定的作用域中调用函数&#xff0c;实际上等于设置函数体内 this 对象的值&#xff0c;真正强大之处在于扩充函数赖以运行的作用域 <2&g…

将要被淘汰的8种人

将要被淘汰的8种人 从朋友的空间你发现了这篇文章&#xff0c;觉得写的很有道理&#xff0c;把它转过来&#xff0c;以便时时提醒一下自己&#xff1a; 不景气的社会&#xff0c;一定会淘汰不争气的人。如何不被淘汰&#xff0c;只要你不在这八种人之内。如果你在其中之内&…

学习 AngularJs 终于有点进步了。

很庆幸&#xff0c;自己在7月股市最低点的时候上车了&#xff0c;&#xff0c;开始了在股市的第一票&#xff0c;现在还不知道会不会赚&#xff0c;虽然买的核电有点偏高&#xff0c;但是还是对国家大工业大工程有信心的&#xff0c;按照今天的走势应该赚不少&#xff0c;希望是…

泽西岛2.9及更高版本中的声明式链接

几个星期前几个月前&#xff0c;我正在寻找如何为Oracle Cloud项目设计新的REST API。 我计划要做的事情之一就是使用Marc Hadley在Jersey 1.x中创建的声明性链接注入。 可悲的是这并没有被转发移植然而&#xff0c;这样一个快速的聊天项目带动和我承担了使代码最新的小中型工作…

使用HTML5技术控制电脑或手机上的摄像头

移动设备和桌面电脑上的客户端API起初并不是同步的。最初总是移动设备上先拥有某些功能和相应的API&#xff0c;但慢慢的&#xff0c;这些API会出现在桌面电脑上。其中一个应用接口技术就是getUserMedia API&#xff0c;它能让应用开发者访问用户的摄像头或内置相机。下面就展示…

好的,每个接触Java字节码的人

Oracle诉Google一案认为&#xff0c;复制Java API的结构&#xff0c;序列和组织是侵犯版权的行为。 侵犯版权不仅是复制行为&#xff0c;而且还适用于拥有该作品副本的所有中间方。 那就是编写/编译任何JVM语言的人&#xff0c;以及在他们拥有的任何设备上都有JAR文件的人&…