Python 中的 property 屬性

原文:Python 中的 property 屬性

從一個實例開始

假設(shè)有天你決定創(chuàng)建一個類瓢宦,用來存儲攝氏溫度刃泡。當(dāng)然這個類也需要實現(xiàn)一個將攝氏溫度轉(zhuǎn)換為華氏溫度的方法怒炸。一種實現(xiàn)的方式如下:

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

我們可以用這個類產(chǎn)生一個對象叨橱,然后按照我們期望的方式改變該對象的溫度屬性:

>>> # create new object
>>> man = Celsius()
 
>>> # set temperature
>>> man.temperature = 37
 
>>> # get temperature
>>> man.temperature
37
 
>>> # get degrees Fahrenheit
>>> man.to_fahrenheit()
98.60000000000001

這里額外的小數(shù)部分是轉(zhuǎn)換成華氏溫度時由于浮點運(yùn)算誤差造成的(你可以在Python解釋器中試試1.1 + 2.2)攻谁。每當(dāng)我們賦值或獲取任何對象的屬性時,例如上面展示的溫度轧叽,Python都會從對象的__dict__字典中搜索它苗沧。

>>> man.__dict__
{'temperature': 37}

因此,man.temperature在其內(nèi)部就變成了man.__dict__['temperature']

現(xiàn)在炭晒,讓我們進(jìn)一步假設(shè)我們的類在客戶中很受歡迎待逞,他們開始在其程序中使用這個類。他們對該類生成的對象做了各種操作网严。有一天识樱,一個受信任的客戶來找我們,建議溫度不能低于-273攝氏度(熱力學(xué)的同學(xué)可能會提出異議屿笼,它實際上是-273.15)牺荠,也被稱為絕對零翁巍÷恳唬客戶進(jìn)一步要求我們實現(xiàn)這個值約束。作為一個以爭取客戶滿意度為己任的公司灶壶,我們很高興地聽從了建議肝断,發(fā)布了1.01版本,升級了我們現(xiàn)有的類驰凛。

使用Getters和Setters

對于上邊的約束胸懈,一個很容易想到的解決方案是隱藏其溫度屬性(使其私有化),并且定義新的用于操作溫度屬性的getter和setter接口恰响∪で可以這么實現(xiàn):

class Celsius:
    def __init__(self, temperature = 0):
        self.set_temperature(temperature)
 
    def to_fahrenheit(self):
        return (self.get_temperature() * 1.8) + 32
 
    # new update
    def get_temperature(self):
        return self._temperature
 
    def set_temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        self._temperature = value

從上邊可以看出,我們定義了兩個新方法get_temperature()和set_temperature()胚宦,此外屬性temperature也被替換為了temperature首有。最前邊的下劃線()用于指示Python中的私有變量。

>>> c = Celsius(-277)
Traceback (most recent call last):
...
ValueError: Temperature below -273 is not possible
 
>>> c = Celsius(37)
>>> c.get_temperature()
37
>>> c.set_temperature(10)
 
>>> c.set_temperature(-300)
Traceback (most recent call last):
...
ValueError: Temperature below -273 is not possible

這個更新成功地實現(xiàn)了新約束枢劝,我們不再允許設(shè)置溫度低于-273度井联。

請注意,Python中實際上是沒有私有變量的您旁。有一些簡單的被遵循的規(guī)范烙常。Python本身不會應(yīng)用任何限制。

>>> c._temperature = -300
>>> c.get_temperature()
-300

但這樣并不會讓人很放心鹤盒。上述更新的最大問題是蚕脏,所有在他們的程序中使用了我們先前類的客戶都必須更改他們的代碼:obj.temperature改為obj.get_temperature()侦副,所有的賦值語句也必須更改,比如obj.temperature = val改為obj.set_temperature(val)驼鞭。這樣的重構(gòu)會給那些擁有成千上萬行代碼的客戶帶來很大的麻煩跃洛。

總而言之,我們的更新是不向后兼容地终议。這就是需要property閃亮登場的地方汇竭。

Property的作用

對于上邊的問題,Python式的解決方式是使用property穴张。這里是我們已經(jīng)實現(xiàn)了的一個版本:

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature
 
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32
 
    def get_temperature(self):
        print("Getting value")
        return self._temperature
 
    def set_temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        print("Setting value")
        self._temperature = value
 
    temperature = property(get_temperature,set_temperature)

我們在get_temperature()和set_temperature()的內(nèi)部增加了一個print()函數(shù)细燎,用來清楚地觀察它們是否正在執(zhí)行。代碼的最后一行皂甘,創(chuàng)建了一個property對象temperature玻驻。簡單地說,property將一些代碼(get_temperature和set_temperature)附加到成員屬性(temperature)的訪問入口偿枕。任何獲取temperature值的代碼都會自動調(diào)用get_temperature()璧瞬,而不是去字典表(dict)中進(jìn)行查找。同樣的渐夸,任何賦給temperature值的代碼也會自動調(diào)用set_temperature()嗤锉。這是Python中一個很酷的功能。我們實際演示一下墓塌。

>>> c = Celsius()
Setting value

從上邊的代碼中我們可以看到瘟忱,即使當(dāng)我們創(chuàng)建一個對象時,set_temperature()也會被調(diào)用苫幢。你能猜到為什么嗎访诱?原因是,當(dāng)一個對象被創(chuàng)建時韩肝,init()方法被調(diào)用触菜。該方法有一行代碼self.temperature = temperature。這個任務(wù)會自動調(diào)用set_temperature()方法哀峻。

>>> c.temperature
Getting value
0

同樣的涡相,對于屬性的任何訪問,例如c.temperature谜诫,也會自動調(diào)用get_temperature()方法漾峡。這就是property所作的事情。這里有一些額外的實例喻旷。

>>> c.temperature = 37
Setting value
 
>>> c.to_fahrenheit()
Getting value
98.60000000000001

我們可以看到生逸,通過使用property,我們在不需要客戶代碼做任何修改的情況下,修改了我們的類槽袄,并實現(xiàn)了值約束烙无。因此我們的實現(xiàn)是向后兼容的,這樣的結(jié)果遍尺,大家都很高興截酷。

最后需要注意的是,實際溫度值存儲在私有變量_temperature中乾戏。屬性temperature是一個property對象迂苛,是用來為這個私有變量提供接口的。

深入挖掘property

在Python中鼓择,property()是一個內(nèi)置函數(shù)三幻,用于創(chuàng)建和返回一個property對象。該函數(shù)的簽名為:

property(fget=None, fset=None, fdel=None, doc=None)

這里呐能,fget是一個獲取屬性值的函數(shù)念搬,fset是一個設(shè)置屬性值的函數(shù),fdel是一個刪除屬性的函數(shù)摆出,doc是一個字符串(類似于注釋)朗徊。從函數(shù)實現(xiàn)上看,這些函數(shù)參數(shù)都是可選的偎漫。所以爷恳,可以按照如下的方式簡單的創(chuàng)建一個property對象。

>>> property()
<property object at 0x0000000003239B38>

Property對象有三個方法骑丸,getter(), setter()和delete()舌仍,用來在對象創(chuàng)建后設(shè)置fget,fset和fdel通危。這就意味著,這行代碼:temperature = property(get_temperature,set_temperature)可以被分解為:

# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)

它們之間是相互等價的灌曙。

熟悉Python中裝飾器decorator的程序員能夠認(rèn)識到上述結(jié)構(gòu)可以作為decorator實現(xiàn)菊碟。我們可以更進(jìn)一步,不去定義名字get_temperature和set_temperature在刺,因為他們不是必須的逆害,并且污染類的命名空間。為此蚣驼,我們在定義getter函數(shù)和setter函數(shù)時重用名字temperature魄幕。下邊的代碼展示如何實現(xiàn)它。

class Celsius:
    def __init__(self, temperature = 0):
        self._temperature = temperature
 
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32
 
    @property
    def temperature(self):
        print("Getting value")
        return self._temperature
 
    @temperature.setter
    def temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        print("Setting value")
        self._temperature = value

上邊的兩種生成property的實現(xiàn)方式颖杏,都很簡單纯陨,推薦使用。在Python尋找property時,你很可能會遇到這種類似的代碼結(jié)構(gòu)翼抠。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咙轩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子阴颖,更是在濱河造成了極大的恐慌活喊,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件量愧,死亡現(xiàn)場離奇詭異钾菊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)偎肃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門结缚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人软棺,你說我怎么就攤上這事红竭。” “怎么了喘落?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵茵宪,是天一觀的道長。 經(jīng)常有香客問我瘦棋,道長稀火,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任赌朋,我火速辦了婚禮凰狞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沛慢。我一直安慰自己赡若,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布团甲。 她就那樣靜靜地躺著逾冬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪躺苦。 梳的紋絲不亂的頭發(fā)上身腻,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天,我揣著相機(jī)與錄音匹厘,去河邊找鬼嘀趟。 笑死,一個胖子當(dāng)著我的面吹牛愈诚,可吹牛的內(nèi)容都是我干的她按。 我是一名探鬼主播牛隅,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼尤溜!你這毒婦竟也來了倔叼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤宫莱,失蹤者是張志新(化名)和其女友劉穎丈攒,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體授霸,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡巡验,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碘耳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片显设。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖辛辨,靈堂內(nèi)的尸體忽然破棺而出捕捂,到底是詐尸還是另有隱情,我是刑警寧澤斗搞,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布指攒,位于F島的核電站,受9級特大地震影響僻焚,放射性物質(zhì)發(fā)生泄漏允悦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一虑啤、第九天 我趴在偏房一處隱蔽的房頂上張望隙弛。 院中可真熱鬧,春花似錦狞山、人聲如沸全闷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽室埋。三九已至,卻和暖如春伊约,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背孕蝉。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工屡律, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人降淮。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓超埋,卻偏偏與公主長得像搏讶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子霍殴,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Python中有個很贊的概念媒惕,叫做property,它使得面向?qū)ο蟮木幊谈雍唵卫赐ァT谠敿?xì)解釋和深入了解Python...
    chen_000閱讀 861評論 0 1
  • 〇妒蔚、前言 本文共108張圖,流量黨請慎重月弛! 歷時1個半月肴盏,我把自己學(xué)習(xí)Python基礎(chǔ)知識的框架詳細(xì)梳理了一遍。 ...
    Raxxie閱讀 18,929評論 17 410
  • //Clojure入門教程: Clojure – Functional Programming for the J...
    葡萄喃喃囈語閱讀 3,629評論 0 7
  • 函數(shù)和對象 1帽衙、函數(shù) 1.1 函數(shù)概述 函數(shù)對于任何一門語言來說都是核心的概念菜皂。通過函數(shù)可以封裝任意多條語句,而且...
    道無虛閱讀 4,543評論 0 5
  • 見字如面厉萝! 一向可好恍飘?多日未見,甚是想念谴垫!距離上次提筆寫信已是好多年前章母,過了二十五歲就自動忘記年齡,直至今...
    漫漫大俠閱讀 321評論 0 0