【廖雪峰python進階筆記】物件導向程式設計
1. 定義類並建立例項
在Python中,類通過 class
關鍵字定義。以 Person 為例,定義一個Person類如下:
class Person(object):
pass
按照 Python 的程式設計習慣,類名以大寫字母
開頭,緊接著是(object)
,表示該類是從哪個類繼承下來的。類的繼承將在後面的章節講解,現在我們只需要簡單地從object類繼承。
有了Person類的定義,就可以建立出具體的xiaoming、xiaohong等例項。建立例項使用 類名+(),類似函式呼叫的形式建立:
xiaoming = Person()
xiaohong = Person()
2. 建立屬性
雖然可以通過Person類建立出xiaoming、xiaohong等例項,但是這些例項看上除了地址不同外,沒有什麼其他不同。在現實世界中,區分xiaoming、xiaohong要依靠他們各自的名字、性別、生日等屬性。
如何讓每個例項擁有各自不同的屬性
?由於Python是動態語言,對每一個例項,都可以直接給他們的屬性賦值,例如,給xiaoming這個例項加上name、gender和birth屬性:
xiaoming = Person()
xiaoming.name = 'Xiao Ming'
xiaoming.gender = 'Male'
xiaoming.birth = '1990-1-1'
給xiaohong加上的屬性不一定要和xiaoming相同:
xiaohong = Person()
xiaohong.name = 'Xiao Hong'
xiaohong.school = 'No. 1 High School'
xiaohong.grade = 2
例項的屬性可以像普通變數一樣進行操作:
xiaohong.grade = xiaohong.grade + 1
例項
請建立包含兩個 Person 類的例項的 list,並給兩個例項的 name 賦值,然後按照 name 進行排序。
class Person(object):
pass
p1 = Person()
p1.name = 'Bart'
p2 = Person()
p2.name = 'Adam'
p3 = Person()
p3.name = 'Lisa'
L1 = [p1, p2, p3]
L2 = sorted(L1, lambda p1, p2: cmp(p1.name, p2.name))
print L2[0].name
print L2[1].name
print L2[2].name
3.初始化屬性
雖然我們可以自由地給一個例項繫結各種屬性,但是,現實世界中,一種型別的例項應該擁有相同名字的屬性。例如,Person類應該在建立的時候就擁有 name、gender 和 birth 屬性,怎麼辦?
在定義 Person 類時,可以為Person類新增一個特殊的__init__()
方法,當建立例項時,__init__()
方法被自動呼叫
,我們就能在此為每個例項都統一加上以下屬性:
class Person(object):
def __init__(self, name, gender, birth):
self.name = name
self.gender = gender
self.birth = birth
__init__()
方法的第一個引數必須是 self
(也可以用別的名字,但建議使用習慣用法),後續引數則可以自由指定,和定義函式沒有任何區別。
相應地,建立例項時,就必須要提供除 self 以外的引數:
xiaoming = Person('Xiao Ming', 'Male', '1991-1-1')
xiaohong = Person('Xiao Hong', 'Female', '1992-2-2')
有了__init__()
方法,每個Person例項在建立時,都會有 name、gender 和 birth 這3個屬性,並且,被賦予不同的屬性值,訪問屬性使用.操作符:
print xiaoming.name
# 輸出 'Xiao Ming'
print xiaohong.birth
# 輸出 '1992-2-2'
要特別注意的是,初學者定義__init__()
方法常常忘記了 self 引數:
>>> class Person(object):
... def __init__(name, gender, birth):
... pass
...
>>> xiaoming = Person('Xiao Ming', 'Male', '1990-1-1')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() takes exactly 3 arguments (4 given)
這會導致建立失敗或執行不正常,因為第一個引數name被Python直譯器傳入了例項的引用
,從而導致整個方法的呼叫引數位置全部沒有對上。
例項
請定義Person類的init方法,除了接受 name、gender 和 birth 外,還可接受任意關鍵字引數,並把他們都作為屬性賦值給例項。
分析
要定義關鍵字引數,使用 **kw;
除了可以直接使用self.name = ‘xxx’設定一個屬性外,還可以通過 setattr(self, ‘name’, ‘xxx’) 設定屬性。
參考程式碼:
class Person(object):
def __init__(self, name, gender, birth, **kw):
self.name = name
self.gender = gender
self.birth = birth
for k, v in kw.iteritems():
setattr(self, k, v)
xiaoming = Person('Xiao Ming', 'Male', '1990-1-1', job='Student')
print xiaoming.name
print xiaoming.job
4. 訪問限制
我們可以給一個例項繫結很多屬性,如果有些屬性不希望被外部訪問到怎麼辦?
Python對屬性許可權的控制是通過屬性名來實現的,如果一個屬性由雙下劃線
開頭(__),該屬性就無法被外部訪問。看例子:
class Person(object):
def __init__(self, name):
self.name = name
self._title = 'Mr'
self.__job = 'Student'
p = Person('Bob')
print p.name
# => Bob
print p._title
# => Mr
print p.__job
# => Error
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute '__job'
可見,只有以雙下劃線開頭的”__job”不能直接被外部訪問。
但是,如果一個屬性以"__xxx__"
的形式定義,那它又可以被外部訪問了,以"__xxx__"
定義的屬性在Python的類中被稱為特殊屬性
,有很多預定義的特殊屬性可以使用,通常我們不要把普通屬性用"__xxx__"
定義。
以單下劃線開頭的屬性"_xxx"
雖然也可以被外部訪問,但是,按照習慣,他們不應該被外部訪問。
5. 建立屬性
類是模板,而例項則是根據類建立的物件。
繫結在一個例項上的屬性不會影響其他例項,但是,類本身也是一個物件,如果在類上繫結一個屬性,則所有例項都可以訪問類的屬性,並且,所有例項訪問的類屬性都是同一個!也就是說,例項屬性每個例項各自擁有,互相獨立,而類屬性有且只有一份。
定義類屬性
可以直接在 class 中定義:
class Person(object):
address = 'Earth'
def __init__(self, name):
self.name = name
因為類屬性是直接繫結在類上
的,所以,訪問類屬性不需要建立例項,就可以直接訪問:
print Person.address
# => Earth
對一個例項呼叫類的屬性也是可以訪問的,所有例項都可以訪問到它所屬的類的屬性:
p1 = Person('Bob')
p2 = Person('Alice')
print p1.address
# => Earth
print p2.address
# => Earth
由於Python是動態語言,類屬性也是可以動態新增和修改
的:
Person.address = 'China'
print p1.address
# => 'China'
print p2.address
# => 'China'
因為類屬性只有一份
,所以,當Person類的address改變時,所有例項訪問到的類屬性都改變了。
例項
請給 Person 類新增一個類屬性 count,每建立一個例項,count 屬性就加 1,這樣就可以統計出一共建立了多少個 Person 的例項。
class Person(object):
count = 0
def __init__(self,name):
self.name = name
Person.count +=1
p1 = Person('Bob')
print Person.count
p2 = Person('Alice')
print Person.count
6. 解決類屬性和例項屬性名字衝突
修改類屬性會導致所有例項訪問到的類屬性全部都受影響,但是,如果在例項變數上修改類屬性
會發生什麼問題呢?
class Person(object):
address = 'Earth'
def __init__(self, name):
self.name = name
p1 = Person('Bob')
p2 = Person('Alice')
print 'Person.address = ' + Person.address
p1.address = 'China'
print 'p1.address = ' + p1.address
print 'Person.address = ' + Person.address
print 'p2.address = ' + p2.address
#結果如下:
Person.address = Earth
p1.address = China
Person.address = Earth
p2.address = Earth
我們發現,在設定了 p1.address = ‘China’ 後,p1訪問 address 確實變成了 ‘China’,但是,Person.address和p2.address仍然是’Earch’,怎麼回事?
原因是 p1.address = ‘China’並沒有改變 Person 的 address,而是給 p1這個例項繫結了例項屬性address ,對p1來說,它有一個例項屬性address(值是’China’),而它所屬的類Person也有一個類屬性address,所以:
訪問 p1.address 時,優先
查詢例項屬性,返回’China’。
訪問 p2.address 時,p2沒有例項屬性address,但是有類屬性address,因此返回’Earth’。
可見,當例項屬性和類屬性重名時,例項屬性優先順序高,它將遮蔽掉對類屬性的訪問
當我們把 p1 的 address 例項屬性刪除後,訪問 p1.address 就又返回類屬性的值 ‘Earth’了:
del p1.address
print p1.address
# => Earth
可見,千萬不要
在例項上修改類屬性,它實際上並沒有修改類屬性,而是給例項繫結了一個例項屬性。
7. 定義例項方法
一個例項的私有屬性就是以__開頭
的屬性,無法被外部訪問,那這些屬性定義有什麼用?
雖然私有屬性無法從外部訪問,但是,從類的內部是可以訪問的。除了可以定義例項的屬性外,還可以定義例項的方法
。
例項的方法就是在類中定義的函式,它的第一個引數
永遠是 self
,指向呼叫該方法的例項本身,其他引數和一個普通函式是完全一樣的:
class Person(object):
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name
get_name(self) 就是一個例項方法,它的第一個引數是self。__init__(self, name)
其實也可看做是一個特殊的例項方法。
呼叫例項方法必須在例項上呼叫:
p1 = Person('Bob')
print p1.get_name() # self不需要顯式傳入
# => Bob
在例項方法內部,可以訪問所有例項屬性
,這樣,如果外部需要訪問私有屬性,可以通過方法呼叫獲得,這種資料封裝的形式除了能保護內部資料一致性外,還可以簡化外部呼叫的難度。
8.方法也是屬性
我們在 class 中定義的例項方法其實也是屬性,它實際上是一個函式物件:
class Person(object):
def __init__(self, name, score):
self.name = name
self.score = score
def get_grade(self):
return 'A'
p1 = Person('Bob', 90)
print p1.get_grade
# => <bound method Person.get_grade of <__main__.Person object at 0x109e58510>>
print p1.get_grade()
# => A
也就是說,p1.get_grade 返回的是一個函式物件,但這個函式是一個繫結到例項的函式,p1.get_grade() 才是方法呼叫。
因為方法也是一個屬性
,所以,它也可以動態地新增到例項上,只是需要用 types.MethodType()
把一個函式變為一個方法:
import types
def fn_get_grade(self):
if self.score >= 80:
return 'A'
if self.score >= 60:
return 'B'
return 'C'
class Person(object):
def __init__(self, name, score):
self.name = name
self.score = score
p1 = Person('Bob', 90)
p1.get_grade = types.MethodType(fn_get_grade, p1, Person)
print p1.get_grade()
# => A
p2 = Person('Alice', 65)
print p2.get_grade()
# ERROR: AttributeError: 'Person' object has no attribute 'get_grade'
# 因為p2例項並沒有繫結get_grade
給一個例項動態新增方法並不常見,直接在class中定義要更直觀。
例項
由於屬性可以是普通的值物件,如 str,int 等,也可以是方法,還可以是函式,大家看看下面程式碼的執行結果,請想一想 p1.get_grade 為什麼是函式而不是方法:
class Person(object):
def __init__(self, name, score):
self.name = name
self.score = score
self.get_grade = lambda: 'A'
p1 = Person('Bob', 90)
print p1.get_grade
print p1.get_grade()
分析
直接把 lambda 函式賦值給 self.get_grade 和繫結方法有所不同,函式呼叫不需要傳入 self,但是方法呼叫需要傳入 self。
9. 定義類方法
和屬性類似,方法也分例項方法
和類方法
。
在class中定義的全部是例項方法,例項方法第一個引數 self 是例項本身。
要在class中定義類方法,需要這麼寫:
class Person(object):
count = 0
@classmethod
def how_many(cls):
return cls.count
def __init__(self, name):
self.name = name
Person.count = Person.count + 1
print Person.how_many()
p1 = Person('Bob')
print Person.how_many()
通過標記一個@classmethod
,該方法將繫結到 Person 類上,而非類的例項。類方法的第一個引數
將傳入類本身
,通常將引數名命名為cls
,上面的 cls.count 實際上相當於 Person.count。
因為是在類上呼叫,而非例項上呼叫,因此類方法無法獲得任何例項變數,只能
獲得類的引用。
相關文章
- 【廖雪峰python進階筆記】函數語言程式設計Python筆記函數程式設計
- 【廖雪峰python進階筆記】模組Python筆記
- 【廖雪峰python進階筆記】定製類Python筆記
- 【廖雪峰python進階筆記】類的繼承Python筆記繼承
- [筆記]物件導向的程式設計筆記物件程式設計
- Java小白進階筆記(5)-進階物件導向Java筆記物件
- 從零學Python:17課-物件導向程式設計(進階)Python物件程式設計
- 【廖雪峰python入門筆記】dictPython筆記
- 【廖雪峰python入門筆記】setPython筆記
- 【廖雪峰python入門筆記】切片Python筆記
- 【廖雪峰python入門筆記】迭代Python筆記
- Python3:物件導向程式設計學習筆記(2)Python物件程式設計筆記
- Python物件導向程式設計Python物件程式設計
- Python 物件導向程式設計Python物件程式設計
- Python進階之物件導向Python物件
- 【廖雪峰python入門筆記】函式Python筆記函式
- 【廖雪峰python入門筆記】變數Python筆記變數
- 【廖雪峰python入門筆記】if語句Python筆記
- 【廖雪峰python入門筆記】for迴圈Python筆記
- Python學習之物件導向高階程式設計Python物件程式設計
- Python 物件導向筆記Python物件筆記
- Python OOP 物件導向程式設計PythonOOP物件程式設計
- python技能--物件導向程式設計Python物件程式設計
- Python物件導向程式設計(1)Python物件程式設計
- Python - 物件導向程式設計 - super()Python物件程式設計
- Python - 物件導向程式設計 - @propertyPython物件程式設計
- 【廖雪峰python入門筆記】列表生成式Python筆記
- 【廖雪峰python入門筆記】list_建立Python筆記
- 【廖雪峰python入門筆記】tuple_建立Python筆記
- 【廖雪峰python入門筆記】while迴圈Python筆記While
- 【廖雪峰python入門筆記】break和continuePython筆記
- 【廖雪峰python入門筆記】多重迴圈Python筆記
- Python進階教程5——物件導向Python物件
- 史上最全 Python 物件導向程式設計Python物件程式設計
- python基礎(物件導向程式設計)Python物件程式設計
- python物件導向程式設計基礎Python物件程式設計
- python之物件導向程式設計(一)Python物件程式設計
- 14 Python物件導向程式設計:反射Python物件程式設計反射