類是建立實例的模板,而實例則是一個一個具體的對象,各個實例擁有的數據都互相獨立,互不影響;html
方法就是與實例綁定的函數,和普通函數不一樣,方法能夠直接訪問實例的數據;python
經過在實例上調用方法,咱們就直接操做了對象內部的數據,但無需知道方法內部的實現細節。編程
和靜態語言不一樣,Python容許對實例變量綁定任何數據,也就是說,對於兩個實例變量,雖然它們都是同一個類的不一樣實例,但擁有的變量名稱均可能不一樣:網絡
>>> bart = Student('Bart Simpson', 59)
>>> lisa = Student('Lisa Simpson', 87)
>>> bart.age = 8
>>> bart.age
8
>>> lisa.age
Traceback (most recent call last):
File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'age'
對象中使用雙下劃線__開頭設置私有變量,外部只能夠經過_類名__變量訪問,可是不建議這樣作。函數
須要注意的是,在Python中,變量名相似__xxx__
的,也就是以雙下劃線開頭,而且以雙下劃線結尾的,是特殊變量,特殊變量是能夠直接訪問的,不是private變量,因此,不能用__name__
、__score__
這樣的變量名。ui
有些時候,你會看到以一個下劃線開頭的實例變量名,好比_name
,這樣的實例變量外部是能夠訪問的,可是,按照約定俗成的規定,當你看到這樣的變量時,意思就是,「雖然我能夠被訪問,可是,請把我視爲私有變量,不要隨意訪問」。spa
雙下劃線開頭的實例變量是否是必定不能從外部訪問呢?其實也不是。不能直接訪問__name
是由於Python解釋器對外把__name
變量改爲了_Student__name
,因此,仍然能夠經過_Student__name
來訪問__name
變量:設計
>>> bart._Student__name 'Bart Simpson'
可是強烈建議你不要這麼幹,由於不一樣版本的Python解釋器可能會把__name
改爲不一樣的變量名。code
總的來講就是,Python自己沒有任何機制阻止你幹壞事,一切全靠自覺。xml
最後注意下面的這種錯誤寫法:
>>> bart = Student('Bart Simpson', 98) >>> bart.get_name() 'Bart Simpson' >>> bart.__name = 'New Name' # 設置__name變量! >>> bart.__name 'New Name'
表面上看,外部代碼「成功」地設置了__name
變量,但實際上這個__name
變量和class內部的__name
變量不是一個變量!內部的__name
變量已經被Python解釋器自動改爲了_Student__name
,而外部代碼給bart
新增了一個__name
變量。不信試試:
>>> bart.get_name() # get_name()內部返回self.__name 'Bart Simpson'
在OOP程序設計中,當咱們定義一個class的時候,能夠從某個現有的class繼承,新的class稱爲子類(Subclass),而被繼承的class稱爲基類、父類或超類(Base class、Super class)。
繼承能夠把父類的全部功能都直接拿過來,這樣就沒必要重零作起,子類只須要新增本身特有的方法,也能夠把父類不適合的方法覆蓋重寫。
動態語言的鴨子類型特色決定了繼承不像靜態語言那樣是必須的。
經過內置的一系列函數,咱們能夠對任意一個Python對象進行剖析,拿到其內部的數據。要注意的是,只有在不知道對象信息的時候,咱們纔會去獲取對象信息。若是能夠直接寫:
sum = obj.x + obj.y
就不要寫:
sum = getattr(obj, 'x') + getattr(obj, 'y')
一個正確的用法的例子以下:
def readImage(fp): if hasattr(fp, 'read'): return readData(fp) return None
假設咱們但願從文件流fp中讀取圖像,咱們首先要判斷該fp對象是否存在read方法,若是存在,則該對象是一個流,若是不存在,則沒法讀取。hasattr()
就派上了用場。
請注意,在Python這類動態語言中,根據鴨子類型,有read()
方法,不表明該fp對象就是一個文件流,它也多是網絡流,也多是內存中的一個字節流,但只要read()
方法返回的是有效的圖像數據,就不影響讀取圖像的功能。
因爲Python是動態語言,根據類建立的實例能夠任意綁定屬性。
編寫程序的時候,千萬不要把實例屬性和類屬性使用相同的名字,由於相同名稱的實例屬性將屏蔽掉類屬性,可是當你刪除實例屬性後,再使用相同的名稱,訪問到的將是類屬性。
數據封裝、繼承和多態只是面向對象程序設計中最基礎的3個概念。在Python中,面向對象還有不少高級特性,容許咱們寫出很是強大的功能。
咱們會討論多重繼承、定製類、元類等概念。
可是,若是咱們想要限制實例的屬性怎麼辦?好比,只容許對Student實例添加name
和age
屬性。
爲了達到限制的目的,Python容許在定義class的時候,定義一個特殊的__slots__
變量,來限制該class實例能添加的屬性:
class Student(object): __slots__ = ('name', 'age') # 用tuple定義容許綁定的屬性名稱
而後,咱們試試:
>>> s = Student() # 建立新的實例 >>> s.name = 'Michael' # 綁定屬性'name' >>> s.age = 25 # 綁定屬性'age' >>> s.score = 99 # 綁定屬性'score' Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'score'
因爲'score'
沒有被放到__slots__
中,因此不能綁定score
屬性,試圖綁定score
將獲得AttributeError
的錯誤。
使用__slots__
要注意,__slots__
定義的屬性僅對當前類實例起做用,對繼承的子類是不起做用的:
>>> class GraduateStudent(Student): ... pass ... >>> g = GraduateStudent() >>> g.score = 9999
除非在子類中也定義__slots__
,這樣,子類實例容許定義的屬性就是自身的__slots__
加上父類的__slots__
。
@property
普遍應用在類的定義中,可讓調用者寫出簡短的代碼,同時保證對參數進行必要的檢查,這樣,程序運行時就減小了出錯的可能性。
class Student(object): @property def birth(self): return self._birth @birth.setter def birth(self, value): self._birth = value @property def age(self): return 2015 - self._birth
經過多重繼承,一個子類就能夠同時得到多個父類的全部功能。
pass # 大類: class Mammal(Animal): pass class Bird(Animal): pass # 各類動物: class Dog(Mammal): pass class Bat(Mammal): pass class Parrot(Bird): pass class Ostrich(Bird): pass
在設計類的繼承關係時,一般,主線都是單一繼承下來的,例如,Ostrich
繼承自Bird
。可是,若是須要「混入」額外的功能,經過多重繼承就能夠實現,好比,讓Ostrich
除了繼承自Bird
外,再同時繼承Runnable
。這種設計一般稱之爲MixIn。
爲了更好地看出繼承關係,咱們把Runnable
和Flyable
改成RunnableMixIn
和FlyableMixIn
。相似的,你還能夠定義出肉食動物CarnivorousMixIn
和植食動物HerbivoresMixIn
,讓某個動物同時擁有好幾個MixIn:
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn): pass
Python的class容許定義許多定製方法,可讓咱們很是方便地生成特定的類。
本節介紹的是最經常使用的幾個定製方法,還有不少可定製的方法,請參考Python的官方文檔。
Enum
能夠把一組相關常量定義在一個class中,且class不可變,並且成員能夠直接比較。
@unique
裝飾器能夠幫助咱們檢查保證沒有重複值。
訪問這些枚舉類型能夠有若干種方法:
>>> day1 = Weekday.Mon >>> print(day1) Weekday.Mon >>> print(Weekday.Tue) Weekday.Tue >>> print(Weekday['Tue']) Weekday.Tue >>> print(Weekday.Tue.value) 2 >>> print(day1 == Weekday.Mon) True >>> print(day1 == Weekday.Tue) False >>> print(Weekday(1)) Weekday.Mon >>> print(day1 == Weekday(1)) True >>> Weekday(7) Traceback (most recent call last): ... ValueError: 7 is not a valid Weekday >>> for name, member in Weekday.__members__.items(): ... print(name, '=>', member) ... Sun => Weekday.Sun Mon => Weekday.Mon Tue => Weekday.Tue Wed => Weekday.Wed Thu => Weekday.Thu Fri => Weekday.Fri Sat => Weekday.Sat
可見,既能夠用成員名稱引用枚舉常量,又能夠直接根據value的值得到枚舉常量。
metaclass是Python中很是具備魔術性的對象,它能夠改變類建立時的行爲。這種強大的功能使用起來務必當心。