Python: 淺淡Python中的屬性(property)
起源:
項目過程中需要研究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.name print ‘Age:‘, a.age a.color = ‘Black‘ print ‘Color:‘, a.color
結果為:
Name: black dog Age: 3 Color: Black
看到了吧?運行期,能動態為實例添加屬性,比如Color。但此方法所添加,只能作用於此實例,而無影響於類
怎麽限制它?用__slots__這個東西,比如,__slots__ = [‘name‘, ‘age‘],則類只能添加這兩個屬性,蛋疼,不好用
如果想要在運行期給類添加屬性,要用到MethodType這東西,比如:
def set_color(self, color): self.color = color Animal.set_color = MethodType(set_color, None, Animal) a1 = Animal(‘yellow dog‘, 3) a1.set_color(‘Yellow‘) print a1.color
……喔,有點淩亂,還是不好用!或者說,心裏沒譜兒。
[email protected]
習慣了高級語言的嚴謹,總想對屬性加以訪問控制,相對安全些,比如直接在__init__中定義公用屬性,從封裝性來說,它是不好的寫法。
屬性之訪問,它亦有機制,[email protected],其獲取、設置函數,須與屬性名一致。
@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 = 300 print ‘Name:‘, a.name print ‘Age:‘, a.age
這樣在設定值時候,總算有個判斷取舍,是不是好一些?
私有變量以_開頭,是種編碼約寫,當然也可以直接訪問它。
不過既如此寫,直接訪問是不推薦的。
若真要為私有變量,則加雙下劃線,比如__name,也一樣阻止不了訪問,但讓我們知道,它不想被直接用到,如下代碼:
class Animal(object): def __init__(self, name): self.__name = name a = Animal(‘black dog‘) print a._Animal__name
這種寫法當真好奇怪!
引廖雪峰言:總的來說就是,Python本身沒有任何機制阻止你幹壞事,一切全靠自覺。
4、property函數
它以一個函數形式,定義一個屬性,[email protected],或者就是它的的變異用法。
其原型為:
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 = 3 print ‘Name:‘, a.name print Animal.name.__doc__ print ‘Age:‘, a.age
其輸出結果一樣,看來只是寫法不同。
後記:
由此可見,Python作為一種解釋型語言,其簡單易用,但其隨意性也大。如此屬性篇,也只是闡述其中一點,其它方面須在使用中去學習留意。
習慣了語法嚴謹,編碼亦請以Pythonic的風格去編寫,python實在沒有多大限制,只要正確計算機都能夠編譯,代碼風格是給人看的,優雅總比零亂好。
如上面幾種屬性寫法,[email protected],相對簡潔一些
參考資料:
Built-in Functions
Python基礎:新式類的屬性訪問
Python深入03 對象的屬性
Python: 淺淡Python中的屬性(property)