Python 的描述符 descriptor详解

论坛 期权论坛     
niminba   2021-5-22 18:52   449   0
<p>Python 在 2.2 版本中引入了descriptor(描述符)功能,也正是基于这个功能实现了新式类(new-styel class)的对象模型,同时解决了之前版本中经典类 (classic class) 系统中出现的多重继承中的 MRO(Method Resolution Order) 问题,另外还引入了一些新的概念,比如 classmethod, staticmethod, super, Property 等。因此理解 descriptor 有助于更好地了解 Python 的运行机制。</p>
<p><strong>那么什么是 descriptor 呢?</strong></p>
<p>简而言之:descriptor 就是一类实现了__get__(), __set__(), __delete__()方法的对象。</p>
<p>Orz...如果你瞬间顿悟了,那么请收下我的膝盖;<br>
O_o!...如果似懂非懂,那么恭喜你!说明你潜力很大,咱们可以继续挖掘:</p>
<p><strong>引言<br>
</strong></p>
<p>对于陌生的事物,一个具体的栗子是最好的学习方式,首先来看这样一个问题:假设我们给一次数学考试创建一个类,用于记录每个学生的学号、数学成绩、以及提供一个用于判断是否通过考试的check 函数:</p>
<div class="blockcode">
<pre class="brush:py;">
class MathScore():
  
  def __init__(self, std_id, score):
    self.std_id = std_id
    self.score = score

  def check(self):
    if self.score &gt;= 60:
      return 'pass'
    else:
      return 'failed'      

</pre>
</div>
<p>很简单一个示例,看起来运行的不错:</p>
<div class="blockcode">
<pre class="brush:py;">
xiaoming = MathScore(10, 90)

xiaoming.score
Out[3]: 90

xiaoming.std_id
Out[4]: 10

xiaoming.check()
Out[5]: 'pass'

</pre>
</div>
<p>但是会有一个问题,比如手一抖录入了一个负分数,那么他就得悲剧的挂了:</p>
<div class="blockcode">
<pre class="brush:py;">
xiaoming = MathScore(10, -90)

xiaoming.score
Out[8]: -90

xiaoming.check()
Out[9]: 'failed'

</pre>
</div>
<p>这显然是一个严重的问题,怎么能让一个数学 90+ 的孩子挂科呢,于是乎一个简单粗暴的方法就诞生了:</p>
<div class="blockcode">
<pre class="brush:py;">
class MathScore():
  
  def __init__(self, std_id, score):
    self.std_id = std_id
    if score &lt; 0:
      raise ValueError("Score can't be negative number!")
    self.score = score

  def check(self):
    if self.score &gt;= 60:
      return 'pass'
    else:
      return 'failed'         
</pre>
</div>
<p>&nbsp;<br>
上面再类的初始化函数中增加了负数判断,虽然不够优雅,甚至有点拙劣,但这在实例初始化时确实工作的不错:</p>
<div class="blockcode">
<pre class="brush:py;">
xiaoming = MathScore(10, -90)

Traceback (most recent call last):

File "&lt;ipython-input-12-6faad631790d&gt;", line 1, in &lt;module&gt;
  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!

</pre>
</div>
<p>OK, 但我们还无法阻止实例对 score 的赋值操作,毕竟修改成绩也是常有的事:</p>
<div class="blockcode">
<pre class="brush:py;">
xiaoming = MathScore(10, 90)

xiaoming = -10  # 无法判断出错误

</pre>
</div>
<p>对于大多数童鞋,这个问题 so easy 的啦:将 score 变为私有,从而禁止 xiaoming.score 这样的直接调用,增加一个 get_score 和 set_score 用于读写:</p>
<div class="blockcode">
<pre class="brush:py;">
class MathScore():
  
  def __init__(self, std_id, score):
    self.std_id = std_id
    if score &lt; 0:
      raise ValueError("Score can't be negative number!")
    self.__score = score

  def check(self):
    if self.__score &gt;= 60:
      return 'pass'
    else:
      return 'failed'      
   
  def get_score(self):
    return self.__score
  
  def set_score(self, value):
    if value &lt; 0:
      raise ValueError("Score can't be negative number!")
    self.__score = value

</pre>
</div>
<p>这确实是种常见的解决方法,但是不得不说这简直丑爆了:</p>
<p>调用成绩再也不能使用 xiaoming.score 这样自然的方式,需要使用 xiaoming.get_score() ,这看起来像口吃在说话!<br>
还有那反人类的下划线和括号...那应该只出现在计算机之间窃窃私语之中...<br>
赋值也无法使用 xiaoming.score = 80, 而需使用 xiaoming.set_score(80), 这对数学老师来说,太 TM 不自然了 !!!</p>
<p>作为一门简洁优雅的编程语言,Python 是不会坐视不管的,于是其给出了 Property 类:</p>
<p><strong>Property 类</strong><br>
</p>
<p>先不管 Property 是啥,咱先看看它是如何简洁优雅的解决上面这个问题的:</p>
<div class="blockcode">
<pre class="brush:py;">
class MathScore():
  
  def __init__(self, std_id, score):
    self.std_id = std_id
    if score &lt; 0:
      raise ValueError("Score can't be negative number!")
    self.__score = score

  def check(self):
    if self.__score &gt;= 60:
      return 'pass'
    else:
      return 'failed'      
   
  def __get_score__(self):
    return self.__score
  
  def __set_score__(self, value):
    if value &lt; 0:
      raise ValueError("Score can't be negative number!")
    self.__score = value
   
  score = property(__get_score__
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1060120
帖子:212021
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP