python描述符学习
一、对象属性的访问控制
看一下这个例子,我们创建一个学生类,提供名字和年龄的属性,然后实例化一个对象,并显示他的信息。
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
stu = Student('zlw', 26)
print(stu.name) # 名字是'zlw'
print(stu.age) # 年龄是26
# 对象的名字和年龄被正常打印,good
# 但是,Student类没办法控制用户在实例化时的行为
stu = Student(26, 'zlw')
print(stu.name) # 名字是26
print(stu.age) # 年龄是'zlw'
我们可以这样做:
class Student:
def __init__(self, name, age):
# 检测每一次实例化时的参数类型
if not isinstance(name, str):
raise TypeError('name必须是str类型')
if not isinstance(age, int):
raise TypeError('age必须是int类型')
self.name = name
self.age = age
我们可以在init函数中控制实例化时的输入类型,不过这有2个问题:
1、只能控制实例化时的类型,对于已经实例化完成的对象,可以直接通过stu.name = 23的形式来重新赋值
2、实例化传递的参数越多,那么类型检测的代码也越多,而且类型检测代码和初始化代码写在一起,两者干的事情不一样,逻辑不是一个层面的,都写在init函数里并不合适
我们可以这样写:
# 省略了age的处理,方式是一样的
class Student:
def __init__(self, name):
if not isinstance(name, str):
raise TypeError('name属性值类型必须是str类型')
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self, value):
if not isinstance(value, str):
raise TypeError('name属性值类型必须是str类型')
self.__name = value
# 这样写就可保证在实例化和已经完成实例化的对象赋值时的类型检查问题了。
# 不过还是有一个问题,就是属性越多,这个类型检查的代码就会不断增加,并且堆积在Student类中
# 我们应该将类型检查的代码单独隔离开,但同时又可以和Student类进行交互
使用python的描述符来处理。
二、描述符基本理解
python的描述符,有几个理解:
1、描述符是一个类
2、描述符的目的是对属性的访问控制
3、描述符将访问控制和业务类分离开,业务类处理业务逻辑,描述符处理属性访问控制
4、描述符是一个特殊类型的类,所谓特殊,就是自定义了__get__
、__set__
、__delete__
方法的类,这种类就叫描述符
5、单独一个描述符没有啥意义,描述符要作用于某一个具体的业务类,为这个业务类提供属性访问控制的功能
6、属性访问可不仅仅是指数据属性的访问,对于函数属性的访问也可以控制。
7、使用类的__setattr__
、__getattr__
、__delattr__
只能全局控制所有属性,而描述符可以有针对的控制部分属性
三、基本使用
class DescName:
dic = {}
def __init__(self, value_type):
self.value_type = value_type
def __get__(self, instance, owner):
print('get running...')
return self.dic.get(instance, None)
def __set__(self, instance, value):
print('set running...')
if not isinstance(value, self.value_type):
raise TypeError('名字必须是str类型')
self.dic[instance] = value
class Student:
name = DescName(str)
def __init__(self, name, age):
self.name = name
self.age = age
stu1 = Student('小明', 26)
print(stu1.name, stu1.age) # 小明,26
stu2 = Student('小红', 25)
print(stu2.name, stu2.age) # 小红,25
上方代码中,我定义了一个Student
类用于表示一个学生。学生有两个属性name
和age
,对于age
我并没有设置任何的控制,对于name
我要求属性值必须是str
类型。我们可以定义一个DescName
描述符类来单独针对name
这个属性提供控制功能。DescName
描述符会对name
的值进行类型判定,如果是非str
类型就会报错。
请注意DesnName
的__set__
方法中的:self.dic[instance] = value
这句代码。
考虑到Student
类会实例化出很多不同的学生对象,这些学生对象是共享同一个描述符对象Student.name
的,因为这是一个类属性,我们要在DescName
描述符中保存不同对象的name
值,就需要区分不同的对象,我这里使用字典,将每一个实例作为字典的key
来保存对应实例的name
值。
不过这里也会有一个问题,就是字典的key
必须是可hash
对象,如果实例是可变对象比如list
,则无法使用这种方法保存属性值,可以考虑转换成一个不可变对象后处理,比如使用字符串表示不同的实例对象。
基本使用的代码中,每个实例对象的属性值实际是保存在共享描述符对象中的。
四、使用描述符完成property、classmethod、staticmethod自定义实现
1、property的自定义实现
# property是一个装饰器,所以底层原理就是把描述符当做一个装饰器并作为类属性存在
class DescName: # 描述符,后续当做装饰器使用,是一个类装饰器,返回的对象是描述符对象
def __init__(self, func): # 首次装饰必须返回一个描述符对象,func是装饰的函数name
self.get_func = func
self.set_func = None
self.delete_func = None # 以上3个属性用于保存用户定义的属性访问逻辑代码
def __get__(self, instance, owner):
return self.get_func(instance) # 访问get的时候返回用户定义的函数输出
def setter(self, func):
self.set_func = func
return self # 提供一个setter用于处理set逻辑,setter是一个函数装饰器
def __set__(self, instance, value):
return self.set_func(instance, value) # 访问set的时候调用用户定义函数
def deleter(self, func): # 提供deleter函数装饰器处理delete逻辑
self.delete_func = func
return self
def __delete__(self, instance):
return self.delete_func(instance) # 访问del的时候调用用户定义函数
class Student: # 业务类
def __init__(self, name, age):
self.__name = name # 在业务类中隐藏属性
self.age = age
@DescName # 和propery是一样的,返回对象就是一个描述符对象,被name引用,且是类属性
def name(self):
return self.__name
@name.setter # set设置
def name(self, value):
if not isinstance(value, str):
raise TypeError('必须是str')
self.__name = value
@name.deleter # 删除设置
def name(self):
print('你不能删除名字属性')
stu1 = Student('zlw', 26) # 实例化的时候会自动调用描述符的set
print(stu1.name, stu1.age) # 打印的时候会调用描述符的get
stu1.name = 'wj'
print(stu1.name, stu1.age)
del stu1.name # 删除的时候调用描述符的delete
2、classmethod的自定义实现
class DescClassMethod: # 描述符,用于作为装饰器,提供类方法功能
def __init__(self, func):
self.get_func = func
self.cls = None # 保存当前类对象
def __get__(self, instance, owner): # 被装饰的时候,保存当前类对象,返回描述符对象
self.cls = owner
return self
def __call__(self, *args, **kwargs): # 类方法一般直接运行,也就是描述符对象被运行
return self.get_func(self.cls, *args, **kwargs) # 传入当前类对象,用户就觉得是自动传入
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def learn(self):
print(f'{self.name} is learning...')
@DescClassMethod # 类方法描述符,让被装饰的函数变成类绑定方法,自动传入类对象
def show_class(cls, school_name):
print(f'this is class --> {cls.__name__}')
print('输入的参数是:', school_name)
stu = Student('zlw', 26)
stu.learn() # 实例方法
Student.show_class('北京大学') # 类方法
3、关于实例方法的思考
# 这里是普通的类和一个实例方法定义,不同对象在调用的时候会将自己传入self
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def learn(self):
print(f'{self.name} is learning...')
stu1 = Student('小明', 26)
stu2 = Student('小红', 25)
stu1.learn() # '小明' is learning...
stu2.learn() # '小红' is learning...
通过描述符,我们可以实现一个类似classmethod
类方法的功能,这功能可以实现当我们调用类方法的时候,python
会自动将类对象传入cls
中。那这就引发另一个问题,对于实例方法,我们在通过实例对象调用的时候,也会将实例对象自己传入self
中,那这个实现和类方法的实现,是不是一样的思路?
仔细查看描述符的__get__
函数可以发现,此函数的参数有instance
和owner
,这个owner
被我们用作cls
,那么,instance
不就代表实例对象吗?如果我们把istance
传入call
,不就可以实现类似实例方法的功能了吗?试试看:
4、实例方法的自定义实现
class SelfMethod: # 描述符,用于处理实例方法功能
def __init__(self, func):
self.get_func = func
self.instance = None # 保存实例对象
def __get__(self, instance, owner):
self.instance = instance # 保存实例对象
return self
def __call__(self, *args, **kwargs):
return self.get_func(self.instance, *args, **kwargs) # 将实例对象传入
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
@SelfMethod # 使用描述符作为装饰器,让learn函数变成实例绑定方法
def learn(self):
print(f'{self.name} is learning...')
stu1 = Student('小明', 26)
stu2 = Student('小红', 25)
stu1.learn() # '小明' is learning... # 调用的时候,会传入stu1实例
stu2.learn() # '小红' is learning... # 调用的时候,会传入stu2实例
print(stu1.__dict__)
print(Student.__dict__) # 类中显示learn是一个描述符对象
以上通过描述符可以自定义类似实例方法的效果,自动传入实例给self
。不过这种实现方式和真正的实例方法实现(暂时还不知道如何实现的)还是有些不同:
a、自定义实现需要@SelfMethod装饰器,而真正实现是不需要装饰器的,不知道是不是底层做了默认省略的操作
b、Student.__dict__
的输出中,自定义的实现表示learn
是一个描述符对象,而真正实现的表示是一个function
对象
c、打印stu1.learn
的时候,自定义实现显示是描述符对象,真正实现表示的是绑定方法对象
嗯。。暂时还没深入研究绑定方法的具体实现,不过可以通过描述符来大概判断出,绑定方法也应该是一个类似封装了实例对象和函数的对象,在被调用的时候也是以func(self, *args, **kw)
的方式来调用的。
5、静态方法的自定义实现
class StaticMethod: # 描述符,用于处理静态方法的类装饰器
def __init__(self, func):
self.get_func = func
def __get__(self, instance, owner):
return self
def __call__(self, *args, **kwargs):
return self.get_func(*args, **kwargs) # 静态方法就是不提供instance和owner参数传入
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
@StaticMethod # 描述函数为静态方法
def show():
print('这里是静态方法show')
stu1 = Student('小明', 26)
stu2 = Student('小红', 25)
stu1.show() # 这里是静态方法show
stu2.show() # 这里是静态方法show
五、总结
1.描述符可以用于处理类级别的属性的访问控制
2.如果描述符处理的是数据属性,那么核心关注点如下
1、描述符的__get__
,__set__
,__delete__
函数处理数据读取、删除
2、处理数据属性的描述符对象,可以保存不同实例对象的属性值,使用字典,key
是实例对象自身,不过要注意是否可hash
3、将描述符作为类装饰器对函数进行装饰,可以自定义类似property
的get
实现效果,不过注意描述符类中要提供setter
和deleter
用于处理set
和del
描述符还可以用于处理函数属性,核心关注点有如下
1、描述符类作为类装饰器对指定函数进行装饰并返回描述符对象
2、返回的描述符对象除了处理描述符三大函数之外,还要提供__call__
函数以便调用
3、使用__call__
函数的时候,传入的是instance
,就类似实例方法,传入的是owner
,就类似类方法classmethod
,什么都不传入,就类似静态方法staticmethod
。
对象属性的解析顺序,比如打印
stu.name
0、无条件优先被__getattribute__
处理
1、获取对象所属类及MRO
列表,自下而上获取name
的第一个定义
2、判断此定义的类型
3、如果类型是数据描述符,直接调用数据描述符的__get__
,否则下一步
4、如果不是数据描述符,判断是否为:实例属性,即stu.__dict__
中是否存在,是的话返回,否则下一步
5、判断类型是否为非数据描述符(也是描述符),如果是,则返回非数据描述符的__get__
返回值,否则下一步
6、判断类型是否是普通属性,比如单纯的一个类属性,如果是,则返回此属性值,否则下一步
7、执行__getattr__
,期望解析顺序最后给出返回值,没定义就报错
python描述符学习的更多相关文章
- python描述符(descriptor)、属性(property)、函数(类)装饰器(decorator )原理实例详解
1.前言 Python的描述符是接触到Python核心编程中一个比较难以理解的内容,自己在学习的过程中也遇到过很多的疑惑,通过google和阅读源码,现将自己的理解和心得记录下来,也为正在为了该问题 ...
- python描述符 descriptor
descriptor 在python中,如果一个新式类定义了__get__, __set__, __delete__方法中的一个或者多个,那么称之为descriptor.descriptor通常用来改 ...
- 杂项之python描述符协议
杂项之python描述符协议 本节内容 由来 描述符协议概念 类的静态方法及类方法实现原理 类作为装饰器使用 1. 由来 闲来无事去看了看django中的内置分页方法,发现里面用到了类作为装饰器来使用 ...
- 【转载】Python 描述符简介
来源:Alex Starostin 链接:www.ibm.com/developerworks/cn/opensource/os-pythondescriptors/ 关于Python@修饰符的文章可 ...
- python描述符descriptor(一)
Python 描述符是一种创建托管属性的方法.每当一个属性被查询时,一个动作就会发生.这个动作默认是get,set或者delete.不过,有时候某个应用可能会有 更多的需求,需要你设计一些更复杂的动作 ...
- Python描述符的使用
Python描述符的使用 前言 作为一位python的使用者,你可能使用python有一段时间了,但是对于python中的描述符却未必使用过,接下来是对描述符使用的介绍 场景介绍 为了引入描述符的使用 ...
- Python描述符 (descriptor) 详解
1.什么是描述符? python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问.这些方法有 __get__(), __set__(), 和__delete__().如 ...
- python描述符和属性查找
python描述符 定义 一般说来,描述符是一种访问对象属性时候的绑定行为,如果这个对象属性定义了__get__(),__set__(), and __delete__()一种或者几种,那么就称之为描 ...
- Iterator Protocol - Python 描述符协议
Iterator Protocol - Python 描述符协议 先看几个有关概念, iterator 迭代器, 一个实现了无参数的 __next__ 方法, 并返回 '序列'中下一个元素,在没有更多 ...
随机推荐
- JMS规范简介
一.JMS规范 Java消息服务定义: Java消息服务(Java Message Service)即JMS,是一个Java平台中面向消息中间件的API,用于在两个应用程序之间或分布式系统中发送/接受 ...
- FPGA时序约束和timequest timing analyzer
FPGA时序约束 时钟约束 #************************************************************** # Create Clock #****** ...
- Hdu1560 DNA sequence(IDA*) 2017-01-20 18:53 50人阅读 评论(0) 收藏
DNA sequence Time Limit : 15000/5000ms (Java/Other) Memory Limit : 32768/32768K (Java/Other) Total ...
- 在Github注册账户
https://github.com/JasonHaoz
- Objective-C 学习笔记(五) 指针
Objective-C 指针 每一个变量是一个内存位置和每一个存储单元都有其定义的地址,可以使用符号(&)的运算符,它表示内存中的地址访问. a. 我们定义一个指针变量 b. 分配一个指针变量 ...
- 使用 pdf.js 查看发票时,显示不了台头和印章的解决办法
系统中使用了 pdf.js 来查看 PDF 文件,发现在查看 电子发票时, 缺失了很多信息, 刚开始以为是 PDF.JS 的 BUG, 后来调试发现只需要在 IIS 里添加一个 bcmap 后缀的 m ...
- CSS3 线性渐变linear-gradient
CSS3 Gradient 分为 linear-gradient(线性渐变)和 radial-gradient(径 向渐变).为了更好的应用 CSS3 Gradient,需要先了解一下目前的几种现代浏 ...
- 微信小程序开发中的二三事之网易云信IMSDK DEMO
本文由作者邹永胜授权网易云社区发布. 简介 为了更好的展示我们即时通讯SDK强悍的能力,网易云信IM SDK微信小程序DEMO的开发就提上了日程.用产品的话说就是: 云信 IM 小程序 SDK 的能力 ...
- web思维导图(前期)
- 数论入门2——gcd,lcm,exGCD,欧拉定理,乘法逆元,(ex)CRT,(ex)BSGS,(ex)Lucas,原根,Miller-Rabin,Pollard-Rho
数论入门2 另一种类型的数论... GCD,LCM 定义\(gcd(a,b)\)为a和b的最大公约数,\(lcm(a,b)\)为a和b的最小公倍数,则有: 将a和b分解质因数为\(a=p1^{a1}p ...