属性描述符是一种Python语言中的特殊对象,用于定义和控制类属性的行为。属性描述符可以通过定义__get__、__set__、__delete__方法来控制属性的读取、赋值和删除操作。
通过使用属性描述符,可以实现对属性的访问控制、类型检查、计算属性等高级功能。
如果一个对象定义了这些方法中的任何一个,它就是一个描述符。
看完上面的文字描述,是不是感觉一头雾水,没关系,接下来通过一个简单的案例来讲解属性描述符的作用。
假设我们现在要做一个成绩管理系统,在定义学生类时,我们可能这样写:
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)
因为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)
虽然上面的代码可以实现参数校验,但是过多的逻辑判断在初始化函数里面,会导致函数特别臃肿,当增加新的参数时,需要增加逻辑判断,一方面重复代码增加,另外也不符合开闭原则。
这个时候该怎么处理呢,我们知道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')。这是怎么做到的呢?
其实,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)
通过上面的定义,也能够实现之前的功能,而且代码重用度更高,看起来也更加简洁。
常见的属性描述符包括数据描述符和非数据描述符。
是指同时定义了
__get__、__set__方法的属性描述符,它可以完全控制属性的读写操作。
是指只定义了
__get__方法的属性描述符,它只能控制属性的读取操作,而不能控制属性的赋值和删除操作。
描述符本质就是一个新式类,在这个新式类中,至少实现了__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__
这里跟属性描述符关系不是特别大,主要是看看属性的搜索顺序。
默认的属性访问是从对象的字典中 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_函数](
我正在尝试设置一个puppet节点,但rubygems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由rubygems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby
我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val
我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah
我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击
我有一个具有一些属性的模型:attr1、attr2和attr3。我需要在不执行回调和验证的情况下更新此属性。我找到了update_column方法,但我想同时更新三个属性。我需要这样的东西:update_columns({attr1:val1,attr2:val2,attr3:val3})代替update_column(attr1,val1)update_column(attr2,val2)update_column(attr3,val3) 最佳答案 您可以使用update_columns(attr1:val1,attr2:val2
我有这个html标记:我想得到这个:我如何使用Nokogiri做到这一点? 最佳答案 require'nokogiri'doc=Nokogiri::HTML('')您可以通过xpath删除所有属性:doc.xpath('//@*').remove或者,如果您需要做一些更复杂的事情,有时使用以下方法遍历所有元素会更容易:doc.traversedo|node|node.keys.eachdo|attribute|node.deleteattributeendend 关于ruby-Nokog
对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs
我不知道为什么,但是当我设置这个设置时它无法编译设置:static_cache_control,[:public,:max_age=>300]这是我得到的syntaxerror,unexpectedtASSOC,expecting']'(SyntaxError)set:static_cache_control,[:public,:max_age=>300]^我只想将“过期”header设置为css、javaascript和图像文件。谢谢。 最佳答案 我猜您使用的是Ruby1.8.7。Sinatra文档中显示的语法似乎是在Ruby1.
1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里
最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总