關(guān)于描述子的詳細(xì)介紹見Python 之描述子。在此僅僅重申:如果一個(gè)對象定義了 __set__()
或者 __delete__()
虑啤,它將被視為一個(gè)數(shù)據(jù)描述子(data descriptor)隙弛。如果僅僅定義了 __get__()
該對象被稱為非數(shù)據(jù)描述子(non-data descriptors)架馋。
為什么要使用描述子呢?我們想象如下場景:使用 Python 模擬學(xué)生的成績單全闷,要求記錄成績叉寂、學(xué)號、姓名总珠,并判斷其是否通過考試屏鳍。
您可能會這樣寫:
class ReportCard:
def __init__(self, name, student_id, score):
self.name = name
self.student_id = student_id
self.score = score
def check(self):
if self.score >= 60:
return '及格'
else:
return '失敗'
這個(gè)實(shí)現(xiàn)看起來沒有什么問題,但是如果錄入分?jǐn)?shù)的時(shí)候姚淆,錄入了負(fù)值孕蝉,如 -90
,上述的 check
將會作出一個(gè)錯(cuò)誤的判斷腌逢。為此降淮,您會想到發(fā)起一個(gè)異常,即:
class ReportCard:
def __init__(self, name, student_id, score):
self.name = name
self.student_id = student_id
if score < 0:
raise ValueError("分?jǐn)?shù)不能是負(fù)值搏讶!")
else:
self.score = score
def check(self):
if self.score >= 60:
return '及格'
else:
return '失敗'
加入佳鳖,成績錄入沒有問題,而若重新修改成績媒惕,此時(shí)也可能設(shè)定為負(fù)值系吩,比如:
a = ReportCard('A', 11, 90)
a.score = -90
這樣又該如何避免意外發(fā)生呢?其實(shí)妒蔚,可以借助 property
修飾符:
class ReportCard:
def __init__(self, name, student_id, score):
self.name = name
self.student_id = student_id
if score < 0:
raise ValueError("分?jǐn)?shù)不能是負(fù)值穿挨!")
else:
self._score = score
def check(self):
if self.score >= 60:
return '及格'
else:
return '失敗'
@property
def score(self):
return self._score
@score.setter
def score(self, new_score):
if new_score < 0:
raise ValueError("分?jǐn)?shù)不能是負(fù)值!")
else:
self._score = new_score
是不是很神奇肴盏?實(shí)際上科盛,property 是基于 descriptor 而實(shí)現(xiàn)的。我們再考慮一個(gè)問題菜皂,錄入成績時(shí)贞绵,學(xué)號也可能錄入為負(fù)值,:cry: 難道還要再次重復(fù)上述的工作恍飘?由此榨崩,可見 property 也存在局限性,且沒有充分利用 Python 的“鴨子類型”的特性章母。既然母蛛,property 是基于 descriptor 而實(shí)現(xiàn)的,為什么不能直接從根源改變呢乳怎?
我們直接定義一個(gè)類溯祸,用于描述非負(fù)數(shù):
class NonNegative:
def __init__(self):
self.dic = {}
def __get__(self, obj, objtype):
print('獲得', obj)
return self.dic[obj]
def __set__(self, obj, value):
print('設(shè)定', obj, value)
if value < 0:
raise ValueError("不能是負(fù)值!")
self.dic[obj] = value
此時(shí),定義成績單將會十分簡潔:
class ReportCard:
score = NonNegative()
std_id = NonNegative()
def __init__(self, name, student_id, score):
self.name = name
self.student_id = student_id
self.score = score
def check(self):
if self.score >= 60:
return '及格'
else:
return '失敗'
我們也可以簡化 NonNegative
:
class NonNegative(dict):
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
self.__dict__ = self
def __get__(self, obj, objtype):
print('獲得', obj)
return self[obj]
def __set__(self, obj, value):
print('設(shè)定', obj, value)
if value < 0:
raise ValueError("不能是負(fù)值焦辅!")
self[obj] = value