Python: 淺淡Python中的屬性(property),pythonproperty
起源:
項目過程中需要研究youtube_dl這個開源組件,翻閱其中對類的使用,對比c#及Delphi中實現,感覺Python屬性機制挺有意思。
區別與進階程式設計語言之單一入口,在類之屬性這一方面,它隨意的太多,以致於習慣了進階語言的嚴謹,對如此隨意心裡倒是有些不安。
也難怪,因為其資料類型弱限制性,往往一個函數返回了一個結果,追溯此傳回值類型,有時需要費上許多工夫!
我不是隨意的人,但隨意起來我還真不是人,用在此處,頗為貼切:b
屬性,是對事物某種特性的抽象,物件導向編程中一個重要概念;區別於欄位,它通常表示為欄位的擴充,加以訪問與設定保護機制。
比如動物,它的顏色、重量,都可以說是它的屬性,此篇以一動物類來做例子,淺探其中屬性機制。
1、新式類和經典類(New-style classes)
Python 2.x預設類為經典類,而對屬性支援完全者為新式類,其區別請自行度娘。
Python 3.x預設即為新式類,不必顯式繼承於object類。
如下面代碼類之定義:
#此為經典類class AnimalClassic: pass#此為新式類,Python 2.2始支援class Animal(object): pass
雖一object差別,但其內在機制,改變頗多。略一窺之,大體如下:
>>>print dir(AnimalClassic)
返回:
['__doc__', '__module__']
>>>print dir(Animal)
返回:
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
由此可知,其加入了諸多物件導向的支援;此節屬性知識,亦基於新式類構建。所以諸君用Python 2.x做開發,寫物件導向代碼時,切記要繼承於object類
2、隨意的屬性
通常,我們這樣定義屬性:
class Animal(object): def __init__(self, name, age): self.name = name self.age = age
即在執行個體初始化時,__init__函數中賦值,其中值可為執行個體訪問:
a = Animal('black dog', 3)print 'Name:', a.nameprint 'Age:', a.agea.color = 'Black'print 'Color:', a.color
結果為:
Name: black dogAge: 3Color: Black
看到了吧?運行期,能動態為執行個體添加屬性,比如Color。但此方法所添加,只能作用於此執行個體,而無影響於類
怎麼限制它?用__slots__這個東西,比如,__slots__ = ['name', 'age'],則類只能添加這兩個屬性,蛋疼,不好用
如果想要在運行期給類添加屬性,要用到MethodType這東西,比如:
def set_color(self, color): self.color = colorAnimal.set_color = MethodType(set_color, None, Animal)a1 = Animal('yellow dog', 3)a1.set_color('Yellow')print a1.color
……喔,有點淩亂,還是不好用!或者說,心裡沒譜兒。
3、@property
習慣了進階語言的嚴謹,總想對屬性加以存取控制,相對安全些,比如直接在__init__中定義公用屬性,從封裝性來說,它是不好的寫法。
屬性之訪問,它亦有機制,其一便是@propery關鍵字。用此關鍵字,其擷取、設定函數,須與屬性名稱一致。
@property可以把一個執行個體方法變成其同名屬性,以支援.號訪問,它亦可標記設定限制,加以規範,如下代碼:
class Animal(object): def __init__(self, name, age): self._name = name self._age = age self._color = 'Black' @property def name(self): return self._name @name.setter def name(self, value): if isinstance(value, basestring): self._name = value else: self._name = 'No name' @property def age(self): return self._age @age.setter def age(self, value): if value > 0 and value < 100: self._age = value else: self._age = 0 # print 'invalid age value.' @property def color(self): return self._color @color.setter def color(self, value): self._color = value; a = Animal('black dog', 3)a.name = 'white dog'a.age = 300print 'Name:', a.nameprint 'Age:', a.age
這樣在設定值時候,總算有個判斷取捨,是不是好一些?
私人變數以_開頭,是種編碼約寫,當然也可以直接存取它。
不過既如此寫,直接存取是不推薦的。
若真要為私人變數,則加雙底線,比如__name,也一樣阻止不了訪問,但讓我們知道,它不想被直接用到,如下代碼:
class Animal(object): def __init__(self, name): self.__name = namea = Animal('black dog')print a._Animal__name
這種寫法當真好奇怪!
引廖雪峰言:總的來說就是,Python本身沒有任何機制阻止你幹壞事,一切全靠自覺。
4、property函數
它以一個函數形式,定義一個屬性,與@property實現原理類似,或者就是它的的變異用法。
其原型為:
property(fget=None, fset=None, fdel=None, doc=None)
譬如上面Animal類,其可用此改為:
class Animal(object): def __init__(self, name, age): self._name = name self._age = age self._color = 'Black' def get_name(self): return self._name def set_name(self, value): if isinstance(value, basestring): self._name = value else: self._name = 'No name' name = property(fget=get_name, fset=set_name, fdel=None, doc='name of an animal') def get_age(self): return self._age def set_age(self, value): if value > 0 and value < 100: self._age = value else: self._age = 0 # print 'invalid age value.' age = property(fget=get_age, fset=set_age, fdel=None, doc='name of an animal') a = Animal('black dog', 3)a.name = 'white dog'a.age = 3print 'Name:', a.nameprint Animal.name.__doc__print 'Age:', a.age
其輸出結果一樣,看來只是寫法不同。
後記:
由此可見,Python作為一種解釋型語言,其簡單易用,但其隨意性也大。如此屬性篇,也只是闡述其中一點,其它方面須在使用中去學習留意。
習慣了文法嚴謹,編碼亦請以Pythonic的風格去編寫,python實在沒有多大限制,只要正確電腦都能夠編譯,代碼風格是給人看的,優雅總比零亂好。
如上面幾種屬性寫法,我尤其喜歡@property方式,相對簡潔一些
參考資料:
Built-in Functions
Python基礎:新式類的屬性訪問
Python深入03 對象的屬性