浅谈对属性描述符__get__、__set__、__delete__的理解
1、属性描述符的基础介绍
1.1 何为属性描述符?
属性描述符是一种Python语言中的特殊对象,用于定义和控制类属性的行为。属性描述符可以通过定义__get__、__set__、__delete__方法来控制属性的读取、赋值和删除操作。
通过使用属性描述符,可以实现对属性的访问控制、类型检查、计算属性等高级功能。
如果一个对象定义了这些方法中的任何一个,它就是一个描述符。
看完上面的文字描述,是不是感觉一头雾水,没关系,接下来通过一个简单的案例来讲解属性描述符的作用。
1.2 为什么需要属性描述符?
假设我们现在要做一个成绩管理系统,在定义学生类时,我们可能这样写:
class Student(object):
def __init__(self, name, age, cn_score, en_score):
self.name = name
self.age = age
self.cn_score = cn_score
self.en_score = en_score
def __str__(self):
return "Student: {},age:{},cn_score:{},en_score:{}".format(self.name, self.age, self.cn_score, self.en_score)
xiaoming = Student("xiaoming", 18, 70, 55)
print(xiaoming)
1.2.1 init函数中做参数校验
因为python是动态语言类型,不像静态语言那样,可以给参数指定类型,所以在传参时,无法得知参数是否正确。比如,当cn_score传入的值为字符串时,程序并不会报错。这个时候,一般就会想到对传入的参数做校验,当传入的参数不符合要求时,抛错。
class Student(object):
def __init__(self, name, age, cn_score, en_score):
self.name = name
if not isinstance(age, int):
raise TypeError("age must be int")
if age <= 0:
raise ValueError("age must be greater than 0")
self.age = age
if not isinstance(cn_score, int):
raise TypeError("cn_score must be int")
if 0 <= cn_score <= 100:
raise ValueError("cn_score must be between 0 and 100")
self.cn_score = cn_score
if not isinstance(en_score, int):
raise TypeError("en_score must be int")
if 0 <= en_score <= 100:
raise ValueError("en_score must be between 0 and 100")
self.en_score = en_score
def __str__(self):
return "Student: {},age:{},cn_score:{},en_score:{}".format(self.name, self.age, self.cn_score, self.en_score)
xiaoming = Student("xiaoming", -1, 70, 55)
print(xiaoming)
虽然上面的代码可以实现参数校验,但是过多的逻辑判断在初始化函数里面,会导致函数特别臃肿,当增加新的参数时,需要增加逻辑判断,一方面重复代码增加,另外也不符合开闭原则。
1.2.2 使用property做参数校验
这个时候该怎么处理呢,我们知道python的内置函数 property可用于装饰方法,使方法之看起来像属性一样。我们可以借助此函数来优化代码,优化后如下:
class Student(object):
def __init__(self, name, age, cn_score, en_score):
self.name = name
self.age = age
self.cn_score = cn_score
self.en_score = en_score
@property
def age(self):
return self.age
@age.setter
def age(self, value):
if not isinstance(value, int):
raise TypeError("age must be int")
if value <= 0:
raise ValueError("age must be greater than 0")
self.age = value
@property
def cn_score(self):
return self.cn_score
@cn_score.setter
def cn_score(self, value):
if not isinstance(value, int):
raise TypeError("cn_score must be int")
if 0 <= value <= 100:
raise ValueError("cn_score must be between 0 and 100")
self.cn_score = value
@property
def en_score(self):
return self.en_score
@en_score.setter
def en_score(self, value):
if not isinstance(value, int):
raise TypeError("en_score must be int")
if 0 <= value <= 100:
raise ValueError("en_score must be between 0 and 100")
self.en_score = value
def __str__(self):
return "Student: {},age:{},cn_score:{},en_score:{}".format(self.name, self.age, self.cn_score, self.en_score)
xiaoming = Student("xiaoming", -1, 70, 55)
print(xiaoming)
现在代码看起来已经挺不错的了,确实。但是想想平常开发中,我们使用Diango 的 ORM 时,定义model时,只需要定义 modle 的属性,就可以使其完成参数的校验,比如ip = models.CharField(max_length=20, db_index=True, verbose_name='IP')。这是怎么做到的呢?
1.2.3 使用属性描述符做参数校验
其实,Django 是使用到了Python的属性描述符 __get__、__set__。接下来,我们使用上面的两个方法,来进行改造。代码如下:
class Score:
def __init__(self, score):
self.score = score
def __get__(self, instance, owner):
return self.score
def __set__(self, instance, value):
if not isinstance(value, int):
raise TypeError("value must be int")
if 0 <= value <= 100:
self.score = value
else:
raise ValueError("value must be between 0 and 100")
class Age:
def __init__(self, age):
self.age = age
def __get__(self, instance, owner):
return self.age
def __set__(self, instance, value):
if not isinstance(value, int):
raise TypeError("age must be int")
if value <= 0:
raise ValueError("age must be greater than 0")
self.age = value
class Student(object):
age = Age(0)
cn_score = Score(0)
en_score = Score(0)
def __init__(self, name, _age, _cn_score, _en_score):
self.name = name
# 通过这里参数名称的区别,我们可以更加明确的知道,是调用
self.age = _age
self.cn_score = _cn_score
self.en_score = _en_score
def __str__(self):
return "Student: {},age:{},cn_score:{},en_score:{}".format(self.name, self.age, self.cn_score, self.en_score)
xiaoming = Student("xiaoming", -1, 70, 55)
print(xiaoming)
通过上面的定义,也能够实现之前的功能,而且代码重用度更高,看起来也更加简洁。
1.3 属性描述符分类
常见的属性描述符包括数据描述符和非数据描述符。
- 数据描述符
是指同时定义了
__get__、__set__方法的属性描述符,它可以完全控制属性的读写操作。
- 非数据描述符
是指只定义了
__get__方法的属性描述符,它只能控制属性的读取操作,而不能控制属性的赋值和删除操作。
2、属性描述符的详细介绍
2.1 属性描述符的调用时机
描述符本质就是一个新式类,在这个新式类中,至少实现了__get__、__set__、__delete__中的一个,这也被称为描述符协议。
__get__():调用一个属性时,触发__set__():为一个属性赋值时,触发__delete__():采用del删除属性时,触发
通过下面的例子将更加清晰的知道 属性描述符的调用时机。
class Age:
def __init__(self, age):
self.age = age
def __get__(self, instance, owner):
print("coming __get__")
return self.age
def __set__(self, instance, value):
print("coming __set__")
if not isinstance(value, int):
raise TypeError("age must be int")
if value <= 0:
raise ValueError("age must be greater than 0")
self.age = value
def __delete__(self, instance):
print("coming __del__")
del self.age
class Student(object):
age = Age(0)
def __init__(self, name):
self.name = name
xiaoming = Student("xiaoming")
xiaoming.age = 9
print(xiaoming.age)
del xiaoming.age
#################
结果:
coming __set__
coming __get__
coming __del__
2.2 属性的搜索顺序
这里跟属性描述符关系不是特别大,主要是看看属性的搜索顺序。
默认的属性访问是从对象的字典中 get, set, 或者 delete 属性。例如a.x的查找顺序是:
a.__getattribute__() -> a.__dict__['age'] -> type(a).__dict__['age'] -> type(a)的基类(不包括元类)-> a.__getattr__ -> 抛错

如果查找的值是对象定义的描述方法之一,python可能会调用描述符方法来重载默认行为,发生在这个查找环节的哪里取决于定义了哪些描述符方法。
1、非数据描述器,实例的属性搜索顺序如下:
a.__getattribute__() -> a.__dict__['age'] -> a.__get__() -> type(a).__dict__['age'] -> type(a)的基类(不包括元类)-> a.__getattr__ -> 抛错。

class Age(object):
def __get__(self, instance, owner):
print("coming __get__")
return "__get__"
# def __set__(self, instance, value):
# print("coming __set__")
# self.age = value
class A2(object):
age = 10
def __init__(self):
self.age = 1000
class A(object):
age = Age()
def __init__(self):
super().__init__()
# def __getattribute__(self, item):
# print("coming __getattribute__")
# return "xxx"
#
def __getattr__(self, item):
print("coming __getattr__")
return "__getattr__"
a = A()
print(a.age)
2、数据描述器,实例的属性搜索顺序如下:
a.__getattribute__() -> a.__get__() -> a.__dict__['age'] -> type(a).__dict__['age'] -> type(a)的基类(不包括元类)-> a.__getattr__ -> 抛错。

class Age(object):
def __get__(self, instance, owner):
print("coming __get__")
return "__get__"
def __set__(self, instance, value):
print("coming __set__")
self.age = value
class A2(object):
def __init__(self):
self.age = 1000
class A(object):
age = Age()
def __init__(self):
self.age = 100
super().__init__()
# def __getattribute__(self, item):
# print("coming __getattribute__")
# return "xxx"
#
def __getattr__(self, item):
print("coming __getattr__")
return "__getattr__"
a = A()
print(a.age)
参考链接:
[属性描述符:__get__函数、__set__函数和__delete_函数](
浅谈对属性描述符__get__、__set__、__delete__的理解的更多相关文章
- 描述符__get__,__set__,__delete__
描述符__get__,__set__,__delete__ # 描述符:1用来代理另外一个类的属性 # __get__():调用一个属性时,触发 # __set__():为一个属性赋值时触发 # __ ...
- python基础----再看property、描述符(__get__,__set__,__delete__)
一.再看property 一个静态属性property ...
- 描述符__get__,__set__,__delete__和析构方法__del__
描述符__get__,__set__,__delete__ 1.描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一 ...
- 描述符__get__(),__set__(),__delete__()(三十七)
http://www.cnblogs.com/linhaifeng/articles/6204014.html#_label12 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__ ...
- python小知识-__call__和类装饰器的结合使用,数据描述符__get__\__set__\__delete__(描述符类是Python中一种用于储存类属性值的对象)
class Decorator(): def __init__(self, f): print('run in init......') self.f = f def __call__(self, a ...
- Python描述符(__get__,__set__,__delete__)简介
先说定义,这里直接翻译官方英文文档: 一般来说,描述符是具有“绑定行为”的对象属性,该对象的属性访问将会被描述符协议中的方法覆盖.这些方法是__get__(),__set__(),和__delete_ ...
- Python类总结-描述符__get__(),__set__(),__delete__()
1 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),set(),delete()中的一个,这也被称为描述符协议 get():调用一个属性时,触发 set():为一 ...
- 元类编程--__get__ __set__属性描述符
from datetime import date, datetime import numbers class IntField: #数据描述符,实现以下任意一个,都会变为属性描述符 def __g ...
- __get__ __set__ __delete__描述符
描述符就是一个新式类,这个类至少要实现__get__ __set__ __delete__方法中的一种class Foo: def __get__(self, instance, owner): pr ...
- Python:高级主题之(属性取值和赋值过程、属性描述符、装饰器)
Python:高级主题之(属性取值和赋值过程.属性描述符.装饰器) 背景 学习了Javascript才知道原来属性的取值和赋值操作访问的“位置”可能不同.还有词法作用域这个东西,这也是我学习任何一门语 ...
随机推荐
- eclipse project is missing required java project
eclipse project is missing required java project eclipse版本: 2022-03 这情况就是maven包出了问题 具体的说不上 看我的解决方法: ...
- k8s重启应用
[root@k8s-master01 opt_k8s]# cat app_list xxx-supervise-srv xxx-recon-srv xxx-mkt-strategy-srv xxx-u ...
- weblogic修改jdk版本方法
首先,得知道JDk 的安装目录: /usr/local/ 第一种方法:weblogic控制台直接指定JDK 版本: 第二种方法:修改weblogic中默认的JDK版本: weblogic/Oracle ...
- element的el-table使用模板插槽在火狐和IE无法显示tooltip(浏览器兼容)
el-table中使用show-overflow-tooltip属性,配合tooltip出现的浏览器兼容性问题 el-table中使用show-overflow-tooltip属性内容过长被隐藏时显示 ...
- greenDao基础用法(一)
1.配置 1.1 在 build.gradle(project) 中,加入这句 classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // a ...
- 【原创】GmSSL Linux编译, 环境搭建
编译环境: wsl ubuntu 20.04 GmSSL Project 算法相关原理文档 由于GmSSL继承自openssl, 为了防止和openssl冲突,最好将GmsSL 编译为静态库 在Lin ...
- 2019之VLC3.071版本Ubuntu 18-win32-64为编译经验记录
编译环境:1.win7+vmware15+Ubuntu 18 64bit虚拟系统(16也可以)2.gcc 7.4 (大于6即可)3.mingw-w64 5.3.0,及其相关联的x86-64的tool, ...
- Docker部署【项目管理和问题跟踪工具-Redmine】
创建网络 docker network create redmine-network 启动Mysql数据库 docker run -d --name mysql --network redmine-n ...
- 基于5G边缘网关的智慧公交站台应用
发展智能物联网,有利于提高城市运行效率,优化居民生活体验,促进城市迸发活力.智能物联网已经融合进生活的方方面面,例如最常见.分布最广泛的公交站台,也能够通过物联网实现升级换代,为居民提供更丰富.更便捷 ...
- .Net中跨域问题的解决方案
开发中前端与后端完全分离并分开发布,遇到跨域问题,一通百度之后,解决方案如下: 把下面的代码放在web.config文件中的 System.WebServer 节点下 <httpProtocol ...