之前开始接触python的时候,阅读过python的入门书籍,那个时候就看过关于python的描述符相关的基本知识,而描述又涉及到属性的获取和设置问题,那个时候可以说是对这部分的介绍知识浏览过去,有个印象。但是最近在阅读项目代码的时候,遇到了__get__和__set__相关功能模块的设计。埃?之前写代码的时候几乎不用这两个内置函数,有什么作用呢?然后一查发现是涉及到了python的描述符,记得之前看过,知道这个概念,现在又遇到了,干脆话点时间把这块知识理解掌握。
开始探究之前,先要把python描述符的基本概念了解一下。描述符:如果一个类定义了__get__()、__set__()和__del__()(这篇文章不讨论这个函数)函数,那么就称之为没描述符。描述符分为非数据描述符(只实现了__get__())和数据描述符(同时实现了__get__()和__set__())。
根据官方文档的介绍:When a class attribute is a descriptor, its special binding behavior is triggered upon attribute lookup. Normally, using a.b to get, set or delete an attribute looks up the object named b in the class dictionary for a, but if b is a descriptor, the respective descriptor method gets called。当一个类的属性是描述符的时候,使用a.b在a中查找b(假设不是描述符)就会调用调用b的__get__的返回值。但是这里要注意的是,描述符是伴随类而存在的,也就是说只有类的属性是一个没描述符的时候,才会又函数__get__或者__set__函数,在类实例中的属性是描述符的时候,调用对应属性是不会触发函数。那么就会有几个疑问需要解决。
①类对数据描述符和非数据描述符属性的获取和赋值分别会有什么表现;
②类实例对数据描述符和非数据描述符属性的获取和赋值分别会有什么表现;
③ 类实例的命名空间中存在与数据描述符或者非数据描述符相同的属性项时有什么表现;
class Descriptor1(object):
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
print "__get__ method"
print instance
print owner
class Descriptor2(Descriptor1):
def __set__(self, instance, val):
print "__set__ method"
print instance
print val
这里通过实现定义两个描述符,一个是数据描述符,另外一个是非数据描述符。然后以这两个描述符对疑问一个个进行解决。
类对数据描述符和非数据描述符属性的获取和赋值分别会有什么表现:
1、类对非数据描述符属性的获取及赋值:
class Demo(object):
prop = Descriptor1("des")
print Demo.prop
Demo.prop = "prop"
print Demo.prop
"""
>>> reload(attr_test)
__get__ method
None
<class 'samples.attr_test.Demo'>
None
prop
"""
表现:类获取描述符属性时,会触发描述符的__get__函数。而对描述符属性赋值的时候,是真正的赋值,将类属性名直接引用了新的值
2、类对数据描述符属性的获取及赋值:这个的表现跟上面的表现是一样,就不再列出代码。
类实例对数据描述符和非数据描述符属性的获取和赋值分别会有什么表现:
1、类实例对非数据描述符属性的获取及赋值:
class Demo(object):
prop = Descriptor1("des")
demo = Demo()
print "demo.__dict__:", demo.__dict__
print "demo.prop:", demo.prop
demo.prop = "hello"
print "demo.__dict__", demo.__dict__
"""
demo.__dict__: {}
demo.prop: __get__ method
<samples.attr_test.Demo object at 0x00000000041E0AC8>
<class 'samples.attr_test.Demo'>
None
demo.__dict__ {'prop': 'hello'}
"""
2、类实例对数据描述符属性的获取以及赋值:
class Demo(object):
prop = Descriptor2("des")
demo = Demo()
print "demo.__dict__:", demo.__dict__
print "demo.prop:", demo.prop
demo.prop = "hello"
print "demo.__dict__", demo.__dict__
"""
demo.__dict__: {}
demo.prop: __get__ method
<samples.attr_test.Demo object at 0x00000000041E0BA8>
<class 'samples.attr_test.Demo'>
None
__set__ method
<samples.attr_test.Demo object at 0x00000000041E0BA8>
hello
demo.__dict__ {}
"""
表现总结:通过对比实例对非数据和数据描述符的获取及赋值可以得到,类实例在获取属性的时候,如果属性是描述符,则会调用__get__,在赋值的时候,对于非数据描述符,没有__set__函数,则直接在实例的命名空间添加一个与描述符同名的属性,而且改属性旨在当前实例具有;对于数据描述符的话,则会调用对应描述符的__set__函数。
类实例的命名空间中存在与数据描述符或者非数据描述符相同的属性项时有什么表现:
1、类实例的命名空间中具有与非数据描述符同名的属性:
class Demo(object):
prop = Descriptor1("des")
def __init__(self):
self.prop = "hello world"
demo = Demo()
print "demo.__dict__:", demo.__dict__
print "demo.prop:", demo.prop
demo.prop = "hello"
print "demo.__dict__", demo.__dict__
"""
demo.__dict__: {'prop': 'hello world'}
demo.prop: hello world
demo.__dict__ {'prop': 'hello'}
"""
2、类实例的命名空间中存在与数据描述符同名的属性:
class Demo(object):
prop = Descriptor2("des")
def __init__(self):
self.prop = "hello world"
demo = Demo()
print "demo.__dict__:", demo.__dict__
print "demo.prop:", demo.prop
demo.prop = "hello"
print "demo.__dict__", demo.__dict__
"""
__set__ method
<samples.attr_test.Demo object at 0x00000000041E0E10>
hello world
demo.__dict__: {}
demo.prop: __get__ method
<samples.attr_test.Demo object at 0x00000000041E0E10>
<class 'samples.attr_test.Demo'>
None
__set__ method
<samples.attr_test.Demo object at 0x00000000041E0E10>
hello
demo.__dict__ {}
"""
表现总结:通过对比,在获取属性或者给属性赋值的时候,是按照数据描述符,命名空间,非数据描述符的顺序进行的。在获取属性值的时候,如果属性是数据描述符,则调用数据描述符的__get__函数返回值作为属性值,如果不是数据描述符,则会在命名空间中寻找对应的属性,直接返回属性值;如果明明空间中也没有的话,则查找非数据描述符,调用非数据描述符的__get__函数且返回值作为属性值。
在解决了这几个主要问题之后,我也在网上搜集了关于python获取属性或者给属性赋值的相关顺序的内容:然后结合我这几个问题的解决,能够进一步掌握关于描述符、python属性寻找策略的相关知识,就先写到这了。