如何理解句子的含义 Python 的 Descriptor

python中基于descriptor的一些概念(上)
python在2.2版本中引入了descriptor功能,也正是基于这个功能实现了新式类(new-styel class)的对象模型,
同时解决了之前版本中经典类(classic class)系统中出现的多重继承中的MRO(Method Resolution Order)的问题,
同时引入了一些新的概念,比如classmethod, staticmethod, super,Property等,这些新功能都是基于descriptor
而实现的。总而言之,通过学习descriptor可以更多地了解python的运行机制。我在这也大概写一个汇总,
写一下对这些东西的理解。欢迎大家讨论。
在这里,为文章中使用的词汇做一下说明:
函数:指的是第一个参数不是self的函数,不在类中定义的函数
方法:指是的第一个参数是self的函数
实例:类的对象,instance
对象模型:就是实现对象行为的整个框架,这里分为经典和新的两种
使用的python版本为python 2.7.2
2. 新式类与经典类
首先来了解一下新式类与经典类的区别,从创建方法上可以明显的看出:
#新式类class C(object):&&&&pass#经典类class B:&&&&pass
简单的说,新式类是在创建的时候继承内置object对象(或者是从内置类型,如list,dict等),而经典类是直
接声明的。使用dir()方法也可以看出新式类中定义很多新的属性和方法,而经典类好像就2个:
这些新的属性和方法都是从object对象中继承过来的。
<h4 id="WizKMOutline_ 内置的object对象
内置的object对象是所有内置,object对象定义了一系列特殊的方法实现所有对象的默认行为。
1. __new__,__init__方法
这两个方法是用来创建object的子类对象,静态方法__new__()用来创建类的实例,然后再调用
__init__()来初始化实例。
2. __delattr__, __getattribute__, __setattr__方法
对象使用这些方法来处理属性的访问
3. __hash__, __repr__, __str__方法
print(someobj)会调用someobj.__str__(), 如果__str__没有定义,则会调用someobj.__repr__(),
__str__()和__repr__()的区别:
默认的实现是没有任何作用的
__repr__的目标是对象信息唯一性
__str__的目标是对象信息的可读性
容器对象的__str__一般使用的是对象元素的__repr__
如果重新定义了__repr__,而没有定义__str__,则默认调用__str__时,调用的是__repr__
也就是说好的编程习惯是每一个类都需要重写一个__repr__方法,用于提供对象的可读信息,
而重写__str__方法是可选的。实现__str__方法,一般是需要更加好看的打印效果,比如你要制作
一个报表的时候等。
可以允许object的子类重载这些方法,或者添加新的方法。
<h4 id="WizKMOutline_ 类的方法
新的对象模型中提供了两种类级别的方法,静态方法和类方法,在诸多新式类的特性中,也只有类方法这个
特性, 和经典对象模型实现的功能一样。
<h5 id="WizKMOutline_.1 静态方法
静态方法可以被类或者实例调用,它没有常规方法的行为(比如绑定,非绑定,默认的第一个self参数),当有一
堆函数仅仅是为了一个类写的时候,采用静态方法声明在类的内部,可以提供行为上的一致性。
创建静态方法的代码如下,使用装饰符@staticmethod进行创建 :
&可以看出,不管是 类调用,还是实例调用静态方法,都是指向同一个函数对象
<h5 id="WizKMOutline_.2 类方法
也是可以通过类和它的实例进行调用,不过它是有默认第一个参数,叫做是类对象,一般被
命名为cls,当然你也可以命名为其它名字,这样就你可以调用类对象的一些操作,
代码如下,使用装饰符@classmethod创建:
<h4 id="WizKMOutline_ 新式类(new-style class)
新式类除了拥有经典类的全部特性之外,还有一些新的特性。比如__init__发生了变化,
新增了静态方法__new__
<h5 id="WizKMOutline_.1 __init__方法
据说在python2.4版本以前,使用新式类时,如果类的初始化方法没有定义,调用的
时候写了多余的参数,编译器不会报错。我现在的python 2.7会报错,还是觉得会报错
比较好点,下面给出新式类和经典类运行这个例子的情况:
<h5 id="WizKMOutline_.2 __new__静态方法
新式类都有一个__new__的静态方法,它的原型是object.__new__(cls[, ...])
cls是一个类对象,当你调用C(*args, **kargs)来创建一个类C的实例时,python的内部调用是
C.__new__(C, *args, **kargs),然后返回值是类C的实例c,在确认
c是C的实例后,python再调用C.__init__(c, *args, **kargs)来初始化实例c。
所以调用一个实例c = C(2),实际执行的代码为:
c = C.__new__(C, 2)if isinstance(c, C):&&&&C.__init__(c, 23)#__init__第一个参数要为实例对象
object.__new__()创建的是一个新的,没有经过初始化的实例。当你重写__new__方法时,可以不
用使用装饰符@staticmethod指明它是静态函数,解释器会自动判断这个方法为静态方法。如果
需要重新绑定C.__new__方法时,只要在类外面执行C.__new__ = staticmethod(yourfunc)就可以了。
可以使用__new__来实现Singleton单例模式:
class Singleton(object):&&&&_singletons = {}&&&&def __new__(cls):&&&&&&&&if not cls._singletons.has_key(cls):&&&&&&&&&&&&#若还没有任何实例&&&&&&&&&&&&cls._singletons[cls] = object.__new__(cls)&&#生成一个实例&&&&&&&&return cls._singletons[cls]&&&&&&&&&&&&&&&&&&&&&&&&&&&&&#返回这个实例
运行结果如下:
使用id()操作,可以看到两个实例指向同一个内存地址。Singleton的所有子类也有这一
特性,只有一个实例对象,如果它的子类定义了__init__()方法,那么必须保证它的
__init__方法能够安全的同一个实例进行多次调用。
<h4 id="WizKMOutline_. 新式类的实例
除了新式类本身具有新的特性外,新式类的实例也具有新的特性。比如它拥有Property功能,该
功能会对属性的访问方式产生影响;还有__slots__新属性,该属性会对生成子类实例产生影响;还
添加了一个新的方法__getattribute__,比原有的__getattr__更加通用。
<h5 id="WizKMOutline_.1 Property
在介绍完descriptor会回过头来讲这个。
<h5 id="WizKMOutline_.2 __slots__属性
通常每一个实例x都会有一个__dict__属性,用来记录实例中所有的属性和方法,也是通过这个字典,
可以让实例绑定任意的属性。而__slots__属性作用就是,当类C有比较少的变量,而且拥有__slots__属性时,
类C的实例 就没有__dict__属性,而是把变量的值存在一个固定的地方。如果试图访问一个__slots__中没有
的属性,实例就会报错。这样操作有什么好处呢?__slots__属性虽然令实例失去了绑定任意属性的便利,
但是因为每一个实例没有__dict__属性,却能有效节省每一个实例的内存消耗,有利于生成小而精
干的实例。
为什么需要这样的设计呢?
在一个实际的企业级应用中,当一个类生成上百万个实例时,即使一个实例节省几十个字节都可以节省
一大笔内存,这种情况就值得使用__slots__属性。
怎么去定义__slots__属性?__slots__是一个类变量,__slots__属性可以赋值一个包含类属性名的字符串元组,或者是可迭代变量,或者
是一个字符串,只要在类定义的时候,使用__slots=aTuple来定义该属性就可以了:
可以看出实例a中没有__dict__字典,而且不能随意添加新的属性,不定义__slots__是可以随意添加的:
使用时__slots__时需要注意的几点:
1.& 当一个类的父类没有定义__slots__属性,父类中的__dict__属性总是可以访问到的,所以只在子
类中定义__slots__属性,而不在父类中定义是没有意义的。
2. 如果定义了__slots属性,还是想在之后添加新的变量,就需要把'__dict__'字符串添加到__slots__的
3. 定义了__slots__属性,还会消失的一个属性是__weakref__,这样就不支持实例的weak reference,
如果还是想用这个功能,同样,可以把'__weakref__'字符串添加到元组里。
4. __slots__功能是通过descriptor实现的,会为每一个变量创建一个descriptor。
5. __slots__的功能只影响定义它的类,因此,子类需要重新定义__slots__才能有它的功能。
<h5 id="WizKMOutline_.3 __getattribute__方法
对新式类的实例来说,所有属性和方法的访问操作都是通过__getattribute__完成,
这是由object基类实现的。如果有特殊的要求,可以重载__getattribute__方法,下面
实现一个不能使用append方法的list:
<h5 id="WizKMOutline_.4 实例的方法
经典的与新的对象模型都允许一个实例拥有私有的属性和方法(可以通过绑定和重绑定)。实例
的私有属性会覆盖掉类中定义的同名属性,举例说明:
然而在python中,隐式调用实例的私有特殊方法时,新的对象模型和经典对象模型表现上不太一样。
在经典对象模型中,无论是显示调用还是隐式调用特殊方法,都会调用实例中后绑定的特殊方法。
而在新的对象模型中,除非显式地调用实例的特殊方法,否则python总是会去调用类中定义的特殊方法,
如果没有定义的话,就报错。代码如下:
调用a[1],将产生一个隐式的__getitem__方法的调用,在新式类中,因为类中没有定义这个方法,也不是
object基类有的方法,所以报错。需要显示地调用才可以运行。
<h4 id="WizKMOutline_ 新的对象模型
在新的对象模型中,继承方式和经典对象模型大体相同,一个关键的区别就是新式类能够从python的内置
类型中继承,而经典类不行。
<h5 id="WizKMOutline_.1 多继承
新式类同样支持多继承,但是如果新式类想要从多个内置类型中继承生成一个新类的话,则这些内置类必须是
经过精心设计,能够互相兼容的。显然,python也没会让你随意的从多个内置类中进行多继承,想创建一个超级类
不是那么容易的。。。通常情况下,至多可以继承一个内置类,比如list, set, dict等。
<h5 id="WizKMOutline_.2 MRO(Method Resolution Order, 方法解析顺序)
对于下图的多继承关系:
b = A(),当调用b.a的时候会发生什么事呢?
在经典对象模型中,方法和属性的查找链是按照从左到右,深度优先的方式进行查找。所以当A的实例b
要使用属性a时,它的查找顺序为:A-&B-&D-&C-&A,这样做就会忽略类C的定义a,而先找到的基类D的
属性a,这是一个bug,这个问题在新式类中得到修复,新的对象模型采用的是从左到右,广度优先的方式
进行查找,所以查找顺序为A-&B-&C-&D,可以正确的返回类C的属性a。
这个顺序的实现是通过新式类中特殊的只读属性__mro__,类型是一个元组,保存着解析顺序信息。只能通过
类来使用,不能通过实例调用。
顺序还和继承时,括号中写的父类顺序有关:
<h5 id="WizKMOutline_.3 协作式调用父类方法
当子类重写了父类的一个方法时,通常会调用父类的同名方法做一些工作,这是比较常见的使用
方式--使用非绑定语法来调用父类的方法。不过在多继承中,这种方法有缺馅:
可以看到,基类A的方法重复运行了两次。怎样才能确保父类中的方法只被顺序的调用一次呢?
在新的对象系统中,有一种特殊的方法super(aclass, obj),可以返回obj实例的一个特殊类型
superobject(超对象, 不是简单的父类的对象),当我们使用超对象调用父类的方法时,就
能保证只被运行一次:
可以看到,D的父类中所有的foo方法都得到执行,并且基类A的foo方法只执行了一次。如果养成了
使用super去调用父类方法的习惯,那么你的类就可以适应无论多么复杂的继承调用结构。super()
可以看成是更加安全调用父类方法的一种新方式。
阅读(...) 评论()Python 的描述符 descriptor详解
投稿:hebedich
字体:[ ] 类型:转载 时间:
Python中包含了许多内建的语言特性,它们使得代码简洁且易于理解。这些特性包括列表/集合/字典推导式,属性(property)、以及装饰器(decorator)。对于大部分特性来说,这些“中级”的语言特性有着完善的文档,并且易于学习。但是这里有个例外,那就是描述符。
Python 在 2.2 版本中引入了descriptor(描述符)功能,也正是基于这个功能实现了新式类(new-styel class)的对象模型,同时解决了之前版本中经典类 (classic class) 系统中出现的多重继承中的 MRO(Method Resolution Order) 问题,另外还引入了一些新的概念,比如 classmethod, staticmethod, super, Property 等。因此理解 descriptor 有助于更好地了解 Python 的运行机制。
那么什么是 descriptor 呢?
简而言之:descriptor 就是一类实现了__get__(), __set__(), __delete__()方法的对象。
Orz...如果你瞬间顿悟了,那么请收下我的膝盖;
O_o!...如果似懂非懂,那么恭喜你!说明你潜力很大,咱们可以继续挖掘:
对于陌生的事物,一个具体的栗子是最好的学习方式,首先来看这样一个问题:假设我们给一次数学考试创建一个类,用于记录每个学生的学号、数学成绩、以及提供一个用于判断是否通过考试的check 函数:
class MathScore():
def __init__(self, std_id, score):
self.std_id = std_id
self.score = score
def check(self):
if self.score &= 60:
return 'pass'
return 'failed'
很简单一个示例,看起来运行的不错:
xiaoming = MathScore(10, 90)
xiaoming.score
Out[3]: 90
xiaoming.std_id
Out[4]: 10
xiaoming.check()
Out[5]: 'pass'
但是会有一个问题,比如手一抖录入了一个负分数,那么他就得悲剧的挂了:
xiaoming = MathScore(10, -90)
xiaoming.score
Out[8]: -90
xiaoming.check()
Out[9]: 'failed'
这显然是一个严重的问题,怎么能让一个数学 90+ 的孩子挂科呢,于是乎一个简单粗暴的方法就诞生了:
class MathScore():
def __init__(self, std_id, score):
self.std_id = std_id
if score & 0:
raise ValueError("Score can't be negative number!")
self.score = score
def check(self):
if self.score &= 60:
return 'pass'
return 'failed'
上面再类的初始化函数中增加了负数判断,虽然不够优雅,甚至有点拙劣,但这在实例初始化时确实工作的不错:
xiaoming = MathScore(10, -90)
Traceback (most recent call last):
File "&ipython-input-12-6faad631790d&", line 1, in &module&
xiaoming = MathScore(10, -90)
File "C:/Users/xu_zh/.spyder2-py3/temp.py", line 14, in __init__
raise ValueError("Score can't be negative number!")
ValueError: Score can't be negative number!
OK, 但我们还无法阻止实例对 score 的赋值操作,毕竟修改成绩也是常有的事:
xiaoming = MathScore(10, 90)
xiaoming = -10
# 无法判断出错误
对于大多数童鞋,这个问题 so easy 的啦:将 score 变为私有,从而禁止 xiaoming.score 这样的直接调用,增加一个 get_score 和 set_score 用于读写:
class MathScore():
def __init__(self, std_id, score):
self.std_id = std_id
if score & 0:
raise ValueError("Score can't be negative number!")
self.__score = score
def check(self):
if self.__score &= 60:
return 'pass'
return 'failed'
def get_score(self):
return self.__score
def set_score(self, value):
if value & 0:
raise ValueError("Score can't be negative number!")
self.__score = value
这确实是种常见的解决方法,但是不得不说这简直丑爆了:
调用成绩再也不能使用 xiaoming.score 这样自然的方式,需要使用 xiaoming.get_score() ,这看起来像口吃在说话!
还有那反人类的下划线和括号...那应该只出现在计算机之间窃窃私语之中...
赋值也无法使用 xiaoming.score = 80, 而需使用 xiaoming.set_score(80), 这对数学老师来说,太 TM 不自然了 !!!
作为一门简洁优雅的编程语言,Python 是不会坐视不管的,于是其给出了 Property 类:
Property 类
先不管 Property 是啥,咱先看看它是如何简洁优雅的解决上面这个问题的:
class MathScore():
def __init__(self, std_id, score):
self.std_id = std_id
if score & 0:
raise ValueError("Score can't be negative number!")
self.__score = score
def check(self):
if self.__score &= 60:
return 'pass'
return 'failed'
def __get_score__(self):
return self.__score
def __set_score__(self, value):
if value & 0:
raise ValueError("Score can't be negative number!")
self.__score = value
score = property(__get_score__, __set_score__)
与上段代码相比,主要是在最后一句实例化了一个 property 实例,并取名为 score, 这个时候,我们就能如此自然的对 instance.__score 进行读写了:
xiaoming = MathScore(10, 90)
xiaoming.score
Out[30]: 90
xiaoming.score = 80
xiaoming.score
Out[32]: 80
xiaoming.score = -90
Traceback (most recent call last):
File "&ipython-input-33-aed&", line 1, in &module&
xiaoming.score = -90
File "C:/Users/xu_zh/.spyder2-py3/temp.py", line 28, in __set_score__
raise ValueError("Score can't be negative number!")
ValueError: Score can't be negative number!
WOW~~一切工作正常!
嗯,那么问题来了:它是怎么工作的呢?
先看下 property 的参数:
class property(fget=None, fset=None, fdel=None, doc=None)& #拷贝自 Python 官方文档
它的工作方式:
实例化 property 实例(我知道这是句废话);
调用 property 实例(比如xiaoming.score)会直接调用 fget,并由 fget 返回相应值;
对 property 实例进行赋值操作(xiaoming.score = 80)则会调用 fset,并由 fset 定义完成相应操作;
删除 property 实例(del xiaoming),则会调用 fdel 实现该实例的删除;
doc 则是该 property 实例的字符说明;
fget/fset/fdel/doc 需自定义,如果只设置了fget,则该实例为只读对象;
这看起来和本篇开头所说的 descriptor 的功能非常相似,让我们回顾一下 descriptor:
“descriptor 就是一类实现了__get__(), __set__(), __delete__()方法的对象。”
@~@ 如果你这次又秒懂了,那么请再次收下我的膝盖 Orz...
另外,Property 还有个装饰器语法糖 @property,其所实现的功能与 property() 完全一样:
class MathScore():
def __init__(self, std_id, score):
self.std_id = std_id
if score & 0:
raise ValueError("Score can't be negative number!")
self.__score = score
def check(self):
if self.__score &= 60:
return 'pass'
return 'failed'
def score(self):
return self.__score
@score.setter
def score(self, value):
#注意方法名称要与上面一致,否则会失效
if value & 0:
raise ValueError("Score can't be negative number!")
self.__score = value
我们知道了 property 实例的工作方式了,那么问题又来了:它是怎么实现的?
事实上 Property 确实是基于 descriptor 而实现的,下面进入我们的正题 descriptor 吧!
descriptor 描述符
照样先不管 descriptor 是啥,咱们还是先看栗子,对于上面 Property 实现的功能,我们可以通过自定义的 descriptor 来实现:
class NonNegative():
def __init__(self):
def __get__(self, ist, cls):
return 'descriptor get: ' + str(ist.__score ) #这里加上字符描述便于看清调用
def __set__(self, ist, value):
if value & 0:
raise ValueError("Score can't be negative number!")
print('descriptor set:', value)
ist.__score = value
class MathScore():
score = NonNegative()
def __init__(self, std_id, score):
self.std_id = std_id
if score & 0:
raise ValueError("Score can't be negative number!")
self.__score = score
def check(self):
if self.__score &= 60:
return 'pass'
return 'failed'
我们新定义了一个 NonNegative 类,并在其内实现了__get__、__set__方法,然后在 MathScore 类中实例化了一个 NonNegative 的实例 score,注意!!!重要的事情说三遍:score 实例是 MathScore 的类属性!!!类属性!!!类属性!!!这个 Mathscore.score 属性同上面 Property 的 score 实例的功能是一样的,只不过 Mathscore.score 调用的 get、set 并不定义在 Mathscore 内,而是定义在 NonNegative 类中,而 NonNegative 类就是一个 descriptor 对象!
纳尼? NonNegative 类的定义中可没见到半个 “descriptor” 的字样,怎么就成了 descriptor 对象???
淡定! 重要的事情这里只说一遍:任何实现 __get__,__set__ 或 __delete__ 方法中一至多个的类,就是 descriptor 对象。所以 NonNegative 自然是一个 descriptor 对象。
那么 descriptor 对象与普通类比有什么特别之处呢? 先不急,来看看上端代码的效果:
xiaoming = MathScore(10, 90)
xiaoming.score
Out[67]: 'descriptor get: 90'
xiaoming.score = 80
descriptor set: 80
wangerma = MathScore(11, 70)
wangerma.score
Out[70]: 'descriptor get: 70'
wangerma.score = 60
Out[70]: descriptor set: 60
wangerma.score
Out[73]: 'descriptor get: 60'
xiaoming.score
Out[74]: 'descriptor get: 80'
xiaoming.score = -90
ValueError: Score can't be negative number!
可以发现,MathScore.score 虽然是一个类属性,但它却可以通过实例的进行赋值,且面对不同的 MathScore 实例 xiaoming、wangerma 的赋值和调用,并不会产生冲突!因此看起来似乎更类似于 MathScore 的实例属性,但与实例属性不同的是它并不通过 MathScore 实例的读写方法操作值,而总是通过 NonNegative 实例的 __get__ 和 __set__ 对值进行操作,那么它是怎么做到这点的?
注意看 __get__、__set__ 的参数
&def __get__(self, ist, cls):& #self:descriptor 实例本身(如 Math.score),ist:调用 score 的实例(如 xiaoming),cls:descriptor 实例所在的类(如MathScore)
&&&&&&& ...
&&& def __set__(self, ist, value):& #score 就是通过这些传入的 ist 、cls 参数,实现对 MathScore 及其具体实例属性的调用和改写的
&&&&&&& ...
OK, 现在我们基本搞清了 descriptor 实例是如何实现对宿主类的实例属性进行模拟的。事实上 Property 实例的实现方式与上面的 NonNegative 实例类似。那么我们既然有了 Propery,为什么还要去自定义 descriptor 呢?
答案在于:更加逼真的模拟实例属性(想想 MathScore.__init__里面那恶心的判断语句),还有最重要的是:代码重用!!!
简而言之:通过单个 descriptor 对象,可以更加逼真的模拟实例属性,并且可以实现对宿主类实例的多个实例属性进行操作。
O.O! 如果你又秒懂了,那么你可以直接跳到下面写评论了...
看个栗子:假如不仅要判断学生的分数是否为负数,而且还要判学生的学号是否为负值,使用 property 的实现方式是这样子的:
class MathScore():
def __init__(self, std_id, score):
if std_id & 0:
raise ValueError("Can't be negative number!")
self.__std_id = std_id
if score & 0:
raise ValueError("Can't be negative number!")
self.__score = score
def check(self):
if self.__score &= 60:
return 'pass'
return 'failed'
def score(self):
return self.__score
@score.setter
def score(self, value):
if value & 0:
raise ValueError("Can't be negative number!")
self.__score = value
def std_id(self):
return self.__std_id
@std_id.setter
def std_id(self, idnum):
if idnum & 0:
raise ValueError("Can't be negative nmuber!")
self.__std_id = idnum
Property 实例最大的问题是:
无法影响宿主类实例的初始化,所以咱必须在__init__ 加上那丑恶的 if ...
单个 Property 实例仅能针对宿主类实例的单个属性,如果需要对多个属性进行控制,则必须定义多个 Property 实例, 这真是太蛋疼了!
但是自定义 descriptor 可以很好的解决这个问题,看下实现:
class NonNegative():
def __init__(self):
self.dic = dict()
def __get__(self, ist, cls):
print('Description get', ist)
return self.dic[ist]
def __set__(self, ist, value):
print('Description set', ist, value)
if value & 0:
raise ValueError("Can't be negative number!")
self.dic[ist] = value
class MathScore():
score = NonNegative()
std_id = NonNegative()
def __init__(self, std_id, score):
#这里并未创建实例属性 std_id 和 score, 而是调用 MathScore.std_id 和 MathScore.score
self.std_id = std_id
self.score = score
def check(self):
if self.score &= 60:
return 'pass'
return 'failed'
哈哈~! MathScore.__init__ 内终于没了 if ,代码也比上面的简洁不少,但是功能一个不少,且实例之间不会相互影响:
事实上,MathScore 多个实例的同一个属性,都是通过单个 MathScore 类的相应类属性(也即 NonNegative 实例)操作的,这同 property 一致,但它又是怎么克服 Property 的两个不足的呢?秘诀有三个:
Property 实例本质上是借助类属性,变向对实例属性进行操作,而 NonNegative 实例则是完全通过类属性模拟实例属性,因此实例属性其实根本不存在;
NonNegative 实例使用字典记录每个 MathScore 实例及其对应的属性值,其中 key 为 MathScore 实例名:比如 score 实例就是使用 dic = {‘Zhangsan':50, ‘Lisi':90} 记录每个实例对应的 score 值,从而确保可以实现对 MathScore 实例属性的模拟;
MathScore 通过在__init__内直接调用类属性,从而实现对实例属性初始化赋值的模拟,而 Property 则不可能,因为 Property 实例(也即MathScore的类属性)是真实的操作 MathScore 实例传入的实例属性以达到目的,但如果在初始化程序中传入的不是实例属性,而是类属性(也即 Property 实例本身),则会陷入无限递归(PS:想一下如果将前一个property 实例实现中的self.__score 改成这里的 self.score 会发生什么)。
这三点看的似懂非懂,没关系,来个比喻:
每个 descriptor 实例(MathScore.score 和 MathScore.std_id)都是类作用域里的一个篮子,篮子里放着写着每个 MathScore 实例名字的盒子(‘zhangsan','lisi‘),同一个篮子里的盒子只记录同样属性的值(比如score篮子里的盒子只记录分数值),当 MathScore 的实例对相应属性进行操作时,则找到对应的篮子,取出标有该实例名字的盒子,并对其进行操作。
因此,实例对应的属性,压根不在实例自己的作用域内,而是在类作用域的篮子里,只不过我们可以通过 xiaoming.score 这样的方式进行操作而已,所以其实际的调用的逻辑是这样的:下图右侧的实例分别通过红线和黑线对score和std_id 进行操作,他们首先通过类调用相应的类属性,然后类属性通过对应的 descriptor 实例作用域对操作进行处理,并返回给类属性相应结果,最后让实例感知到。
看到这里,很多童鞋可能不淡定了,因为大家都知道在 Python 中采取 xiaoming.score = 10 这样的赋值方式,如果 xiaoming 没有 score 这样的实例属性,必定会自动创建该实例属性,怎么会去调用 MathScore 的 score 呢?
首先,要鼓掌!!! 给想到这点的童鞋点赞!!!其实上面在说 Property 的时候这个问题就产生了。
其次,Python 为了实现 discriptor 确实对属性的调用顺序做出了相应的调整,这些将会“Python 的 descriptor(下)”中介绍。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具}

我要回帖

更多关于 如何理解文化的功能 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信