Python中属性和描述符的正确使用
2016-08-23 00:00
639 查看
关于@property装饰器
在Python中我们使用@property装饰器来把对函数的调用伪装成对属性的访问。
那么为什么要这样做呢?因为@property让我们将自定义的代码同变量的访问/设定联系在了一起,同时为你的类保持一个简单的访问属性的接口。
举个栗子,假如我们有一个需要表示电影的类:
你开始在项目的其他地方使用这个类,但是之后你意识到:如果不小心给电影打了负分怎么办?你觉得这是错误的行为,希望Movie类可以阻止这个错误。 你首先想到的办法是将Movie类修改为这样:
但这行不通。因为其他部分的代码都是直接通过
Python的property解决了这个问题。
我们可以这样做
这样在任何地方修改
property的不足
对property来说,最大的缺点就是它们不能重复使用。举个例子,假设你想为
下面是修改过的新类:
可以看到代码增加了不少,但重复的逻辑也出现了不少。虽然property可以让类从外部看起来接口整洁漂亮,但是却做不到内部同样整洁漂亮。
描述符登场
什么是描述符?
一般来说,描述符是一个具有绑定行为的对象属性,其属性的访问被描述符协议方法覆写。这些方法是
描述符有什么作用?
The default behavior for attribute access is to get, set, or delete the attribute from an object's dictionary. For instance, a.x has a lookup chain starting witha.__dict__[‘x'], then type(a).__dict__[‘x'], and continuing through the base classes of type(a) excluding metaclasses. If the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined.—–摘自官方文档
简单的说描述符会改变一个属性的基本的获取、设置和删除方式。
先看如何用描述符来解决上面 property逻辑重复的问题。
因为描述符优先级高并且会改变默认的
不过我们也总不能用下面这样的方式来创建实例。
这样太生硬了,所以我们还缺一个构造函数。
这样在获取、设置和删除
现在虽然问题得到了解决,但是你可能会好奇这个描述符到底是如何工作的。具体来说,在
描述符如何工作
看官方的说明
If an object defines both __get__()and __set__(), it is considered a data descriptor. Descriptors that only define __get__()are called non-data descriptors (they are typically used for methods but other uses are possible).
Data and non-data descriptors differ in how overrides are calculated with respect to entries in an instance's dictionary. If an instance's dictionary has an entry with the same name as a data descriptor, the data descriptor takes precedence. If an instance's dictionary has an entry with the same name as a non-data descriptor, the dictionary entry takes precedence.
The important points to remember are:
descriptors are invoked by the __getattribute__() method
overriding __getattribute__() prevents automatic descriptor calls
object.__getattribute__() and type.__getattribute__() make different calls to __get__().
data descriptors always override instance dictionaries.
non-data descriptors may be overridden by instance dictionaries.
类调用
下面是摘自国外一篇博客上的内容。
Given a Class “C” and an Instance “c” where “c = C(…)”, calling “c.name” means looking up an Attribute “name” on the Instance “c” like this:
Get the Class from Instance
Call the Class's special method getattribute__. All objects have a default __getattribute
Inside getattribute
Get the Class's mro as ClassParents
For each ClassParent in ClassParents
If the Attribute is in the ClassParent's dict
If is a data descriptor
Return the result from calling the data descriptor's special method __get__()
Break the for each (do not continue searching the same Attribute any further)
If the Attribute is in Instance's dict
Return the value as it is (even if the value is a data descriptor)
For each ClassParent in ClassParents
If the Attribute is in the ClassParent's dict
If is a non-data descriptor
Return the result from calling the non-data descriptor's special method __get__()
If it is NOT a descriptor
Return the value
If Class has the special method getattr
Return the result from calling the Class's special method__getattr__.
我对上面的理解是,访问一个实例的属性的时候是先遍历它和它的父类,寻找它们的
描述符的应用场景
python的property、classmethod修饰器本身也是一个描述符,甚至普通的函数也是描述符(non-data discriptor)
django model和SQLAlchemy里也有描述符的应用
总结
只有当确实需要在访问属性的时候完成一些额外的处理任务时,才应该使用property。不然代码反而会变得更加啰嗦,而且这样会让程序变慢很多。以上就是本文的全部内容,由于个人能力有限,文中如有笔误、逻辑错误甚至概念性错误,还请提出并指正。
Python黑魔法Descriptor描述符的实例解析
Python中的Descriptor描述符学习教程
Python 的描述符 descriptor详解
解密Python中的描述符(descriptor)
Python中的类与对象之描述符详解
python的描述符(descriptor)、装饰器(property)造成的一个无限递归问题分享
实例讲解Python中的私有属性
Python深入学习之对象的属性
python 基础学习第二弹 类属性和实例属性
在Python中我们使用@property装饰器来把对函数的调用伪装成对属性的访问。
那么为什么要这样做呢?因为@property让我们将自定义的代码同变量的访问/设定联系在了一起,同时为你的类保持一个简单的访问属性的接口。
举个栗子,假如我们有一个需要表示电影的类:
class Movie(object): def __init__(self, title, description, score, ticket): self.title = title self.description = description self.score = scroe self.ticket = ticket
你开始在项目的其他地方使用这个类,但是之后你意识到:如果不小心给电影打了负分怎么办?你觉得这是错误的行为,希望Movie类可以阻止这个错误。 你首先想到的办法是将Movie类修改为这样:
class Movie(object): def __init__(self, title, description, score, ticket): self.title = title self.description = description self.ticket = ticket if score < 0: raise ValueError("Negative value not allowed:{}".format(score)) self.score = scroe
但这行不通。因为其他部分的代码都是直接通过
Movie.score来赋值的。这个新修改的类只会在
__init__方法中捕获错误的数据,但对于已经存在的类实例就无能为力了。如果有人试着运行
m.scrore= -100,那么谁也没法阻止。那该怎么办?
Python的property解决了这个问题。
我们可以这样做
class Movie(object): def __init__(self, title, description, score): self.title = title self.description = description self.score = score self.ticket = ticket @property def score(self): return self.__score @score.setter def score(self, score): if score < 0: raise ValueError("Negative value not allowed:{}".format(score)) self.__score = score @score.deleter def score(self): raise AttributeError("Can not delete score")
这样在任何地方修改
score都会检测它是否小于0。
property的不足
对property来说,最大的缺点就是它们不能重复使用。举个例子,假设你想为
ticket字段也添加非负检查。
下面是修改过的新类:
class Movie(object): def __init__(self, title, description, score, ticket): self.title = title self.description = description self.score = score self.ticket = ticket @property def score(self): return self.__score @score.setter def score(self, score): if score < 0: raise ValueError("Negative value not allowed:{}".format(score)) self.__score = score @score.deleter def score(self): raise AttributeError("Can not delete score") @property def ticket(self): return self.__ticket @ticket.setter def ticket(self, ticket): if ticket < 0: raise ValueError("Negative value not allowed:{}".format(ticket)) self.__ticket = ticket @ticket.deleter def ticket(self): raise AttributeError("Can not delete ticket")
可以看到代码增加了不少,但重复的逻辑也出现了不少。虽然property可以让类从外部看起来接口整洁漂亮,但是却做不到内部同样整洁漂亮。
描述符登场
什么是描述符?
一般来说,描述符是一个具有绑定行为的对象属性,其属性的访问被描述符协议方法覆写。这些方法是
__get__()、
__set__()和
__delete__(),一个对象中只要包含了这三个方法中的至少一个就称它为描述符。
描述符有什么作用?
The default behavior for attribute access is to get, set, or delete the attribute from an object's dictionary. For instance, a.x has a lookup chain starting witha.__dict__[‘x'], then type(a).__dict__[‘x'], and continuing through the base classes of type(a) excluding metaclasses. If the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined.—–摘自官方文档
简单的说描述符会改变一个属性的基本的获取、设置和删除方式。
先看如何用描述符来解决上面 property逻辑重复的问题。
class Integer(object): def __init__(self, name): self.name = name def __get__(self, instance, owner): return instance.__dict__[self.name] def __set__(self, instance, value): if value < 0: raise ValueError("Negative value not allowed") instance.__dict__[self.name] = value class Movie(object): score = Integer('score') ticket = Integer('ticket')
因为描述符优先级高并且会改变默认的
get、
set行为,这样一来,当我们访问或者设置
Movie().score的时候都会受到描述符
Integer的限制。
不过我们也总不能用下面这样的方式来创建实例。
a = Movie() a.score = 1 a.ticket = 2 a.title = ‘test' a.descript = ‘…'
这样太生硬了,所以我们还缺一个构造函数。
class Integer(object): def __init__(self, name): self.name = name def __get__(self, instance, owner): if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): if value < 0: raise ValueError('Negative value not allowed') instance.__dict__[self.name] = value class Movie(object): score = Integer('score') ticket = Integer('ticket') def __init__(self, title, description, score, ticket): self.title = title self.description = description self.score = score self.ticket = ticket
这样在获取、设置和删除
score和
ticket的时候都会进入
Integer的
__get__、
__set__,从而减少了重复的逻辑。
现在虽然问题得到了解决,但是你可能会好奇这个描述符到底是如何工作的。具体来说,在
__init__函数里访问的是自己的
self.score和
self.ticket,怎么和类属性
score和
ticket关联起来的?
描述符如何工作
看官方的说明
If an object defines both __get__()and __set__(), it is considered a data descriptor. Descriptors that only define __get__()are called non-data descriptors (they are typically used for methods but other uses are possible).
Data and non-data descriptors differ in how overrides are calculated with respect to entries in an instance's dictionary. If an instance's dictionary has an entry with the same name as a data descriptor, the data descriptor takes precedence. If an instance's dictionary has an entry with the same name as a non-data descriptor, the dictionary entry takes precedence.
The important points to remember are:
descriptors are invoked by the __getattribute__() method
overriding __getattribute__() prevents automatic descriptor calls
object.__getattribute__() and type.__getattribute__() make different calls to __get__().
data descriptors always override instance dictionaries.
non-data descriptors may be overridden by instance dictionaries.
类调用
__getattribute__()的时候大概是下面这样子:
def __getattribute__(self, key): "Emulate type_getattro() in Objects/typeobject.c" v = object.__getattribute__(self, key) if hasattr(v, '__get__'): return v.__get__(None, self) return v
下面是摘自国外一篇博客上的内容。
Given a Class “C” and an Instance “c” where “c = C(…)”, calling “c.name” means looking up an Attribute “name” on the Instance “c” like this:
Get the Class from Instance
Call the Class's special method getattribute__. All objects have a default __getattribute
Inside getattribute
Get the Class's mro as ClassParents
For each ClassParent in ClassParents
If the Attribute is in the ClassParent's dict
If is a data descriptor
Return the result from calling the data descriptor's special method __get__()
Break the for each (do not continue searching the same Attribute any further)
If the Attribute is in Instance's dict
Return the value as it is (even if the value is a data descriptor)
For each ClassParent in ClassParents
If the Attribute is in the ClassParent's dict
If is a non-data descriptor
Return the result from calling the non-data descriptor's special method __get__()
If it is NOT a descriptor
Return the value
If Class has the special method getattr
Return the result from calling the Class's special method__getattr__.
我对上面的理解是,访问一个实例的属性的时候是先遍历它和它的父类,寻找它们的
__dict__里是否有同名的
data descriptor如果有,就用这个
data descriptor代理该属性,如果没有再寻找该实例自身的
__dict__,如果有就返回。任然没有再查找它和它父类里的
non-data descriptor,最后查找是否有
__getattr__
描述符的应用场景
python的property、classmethod修饰器本身也是一个描述符,甚至普通的函数也是描述符(non-data discriptor)
django model和SQLAlchemy里也有描述符的应用
class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True) email = db.Column(db.String(120), unique=True) def __init__(self, username, email): self.username = username self.email = email def __repr__(self): return '<User %r>' % self.username
总结
只有当确实需要在访问属性的时候完成一些额外的处理任务时,才应该使用property。不然代码反而会变得更加啰嗦,而且这样会让程序变慢很多。以上就是本文的全部内容,由于个人能力有限,文中如有笔误、逻辑错误甚至概念性错误,还请提出并指正。
您可能感兴趣的文章:
详解Python中的Descriptor描述符类Python黑魔法Descriptor描述符的实例解析
Python中的Descriptor描述符学习教程
Python 的描述符 descriptor详解
解密Python中的描述符(descriptor)
Python中的类与对象之描述符详解
python的描述符(descriptor)、装饰器(property)造成的一个无限递归问题分享
实例讲解Python中的私有属性
Python深入学习之对象的属性
python 基础学习第二弹 类属性和实例属性
相关文章推荐
- Python中属性和描述符的正确使用
- javascript操作cookies 以及 正确使用cookies的属性
- img标签中alt和title属性的正确使用
- Extjs 利用panel html属性加入DIV实现如TextArea的效果,并且能正确使用html标签,实现如向textarea文本域嵌入按钮button或其他控件,,而且兼容firefox
- 服务器控件使用eval()绑定属性出现服务器标记的格式不正确
- 【转】javascript操作cookies 以及 正确使用cookies的属性
- .NET中正确使用数据控件的VALUE属性
- 如何理解并正确使用python中的self
- javascript操作cookies 以及正确使用cookies的属性
- CSS属性的使用描述
- python使用os.getcwd()获取当前路径不正确
- javascript操作cookies 以及 正确使用cookies的属性
- 正确的使用position属性(转)
- 正确的使用position属性
- 【转】javascript操作cookies 以及 正确使用cookies的属性
- img标签中alt和title属性的正确使用
- 类库DLL属性说明和正确使用App_Code中类的静态成员
- 【转】javascript操作cookies 以及 正确使用cookies的属性
- 该如何正确的使用position属性它的作用是什么?
- [Python]面向对象--属性和方法命名使用