Class
類的定義以及實例的建立
Python中,類通過 class 關鍵字定義。
例如最簡單的一個類定義可以為:
class Person(object): pass
Python 的編程習慣,類名以大寫字母開頭,緊接着是(object),表示該類是從哪個類繼承下來的。
例如上面的person類,我們可以創建很多實例:
zhangsan=Person()
lisi=Person()
實例的屬性及方法
Python是動態語言,對每一個實例,都可以直接給他們的屬性賦值,即使類中並無定義,例如,給xiaoming這個實例加上name、gender和birth屬性:
xiaoming = Person() xiaoming.name = 'Xiao Ming' xiaoming.gender = 'Male' xiaoming.birth = '1990-1-1'
__init__()
如上所說,我們可以自由地給一個實例綁定各種屬性,但是,現實世界中,一種類型的實例應該擁
有相同名字的屬性。例如,Person類應該在創建的時候就擁有 name、gender 和 birth 屬性。
所以在創建類時,我們可以使用__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')
屬性的訪問限制(私有屬性)
我們可以給一個實例綁定很多屬性,如果有些屬性不希望被外部訪問到怎么辦?
Python對屬性權限的控制是通過屬性名來實現的,如果一個屬性由雙下划線開頭(__),該屬性就無
法被外部訪問。例如:
class Person(object): def __init__(self, name): self.name = name self._title = 'Mr' self.__job = 'Student' p1=Person("zhangsan") print(p1.name,'\n') print(p1._title,'\n') try: print(p1.__job) except: print("Error")
結果:
zhangsan
Mr
Error
可以看出只有__job無法被訪問
實例的方法
雖然私有屬性無法從外部訪問,但是,從類的內部是可以訪問的。除了可以定義實例的屬性外,還可以定義實例的方法。
例如我們可以定義一個__name私有屬性,同時創建一個get_name方法去訪問__name:
class Person(object): def __init__(self, name): self.__name = name def get_name(self): return self.__name p2=Person("Bob") print(p2.get_name())
結果
Bob
get_name(self) 就是一個實例方法,它的第一個參數是self。__init__(self, name)其實也可看做是一個特殊的實例方法。
在實例方法內部,可以訪問所有實例屬性,這樣,如果外部需要訪問私有屬性,可以通過方法調用獲得,這種數據封裝的形式除了能保護內部數據一致性外,還可以簡化外部調用的難度。
python中實例方法也是屬性
我們在 class 中定義的實例方法其實也是屬性,它實際上是一個函數對象,因為方法也是一個屬性,所以,它也可以動態地添加到實例上,只是需要用 types.MethodType() 把一個函數變為一個方法:
import types def Pn_get_name(self): return str("This person's name is :"+self.name) class Person(object): def __init__(self, name): self.name = name p1=Person("Alice") p2=Person("Bob") p1.get_name=types.MethodType(Pn_get_name,p1) print(p1.get_name(),'\n') print(p2.get_name())
結果:
This person's name is :Alice Traceback (most recent call last): File "D:/pythonwork/practise/test.py", line 15, in <module> print(p2.get_name()) AttributeError: 'Person' object has no attribute 'get_name'
給一個實例動態添加方法並不常見,直接在class中定義要更直觀
類的屬性及方法
類是模板,而實例則是根據類創建的對象。綁定在一個實例上的屬性不會影響其他實例,但是,類本身也是一個對象,如果在類上綁定一個屬性,則所有實例都可以訪問類的屬性,並且,所有實例訪問的類屬性都是同一個,也就是說,實例屬性每個實例各自擁有,互相獨立,而類屬性有且只有一份。
定義類屬性可以直接在 class 中定義:
class Person(object): address = 'Earth' def __init__(self, name): self.name = name
因為類屬性是直接綁定在類上的,所以,訪問類屬性可以直接用類名訪問,也可以使用實例訪問,例如:
class Person(object): address = 'Earth' def __init__(self, name): self.name = name p1=Person("zhangsan") print(Person.address) print(p1.address)
結果:
Earth
Earth
由於Python是動態語言,類屬性也是可以動態添加和修改的:
Person.address = 'China' print(Person.address) print(p1.address)
結果:
China
China
因為類屬性只有一份,所以,當Person類的address改變時,所有實例訪問到的類屬性都改變了。
注意:如果在實例變量上修改類屬性,則實際上是給實例綁定了一個與類屬性同名的實例屬性,而
訪問時會優先訪問實例屬性:
p1.address="China" print(Person.address) print(p1.address)
結果:
Earth
China
類的方法
和屬性類似,方法也分實例方法和類方法。
在class中定義的全部是實例方法,實例方法第一個參數 self 是實例本身。
要在class中定義類方法,我們通過標記一個 @classmethod,該方法將綁定到 Person 類上,而非
類的實例。類方法的第一個參數將傳入類本身,通常將參數名命名為 cls,下面例子中
的 cls.count 實際上相當於 Person.count。
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())
結果:
0
1
類的繼承
如果已經定義了Person類,需要定義新的Student和Teacher類時,可以直接從Person類繼承,定義Student類時,只需要把額外的屬性加上,例如score:
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender class Student(Person): def __init__(self, name, gender, score): super(Student, self).__init__(name, gender) self.score = score
注意:
一定要用 super(Student, self).__init__(name, gender) 去初始化父類,否則,繼承自 Person 的 Student 將沒有 name 和 gender。
函數super(Student, self)將返回當前類繼承的父類,即 Person ,然后調用__init__()方法,注意self參數已在super()中傳入,在__init__()中將隱式傳遞,不需要寫出(也不能寫)。
python中的多態
類具有繼承關系,並且子類類型可以向上轉型看做父類類型,如果我們從 Person 派生出 Student和
Teacher ,並都寫了一個 同名的whoAmI() 方法,當我們調用實例的whoAmI() 方法時,總是先查找
它自身的定義,如果沒有定義,則順着繼承鏈向上查找,直到在某個父類中找到為止。
例如:
class Person(object): pass def Pstr(self): print("This is Person class method") class Student(Person): def __init__(self, name): self.name = name def Pstr(self): print("This is Student class method") class Teacher(Person): def __init__(self, name): self.name = name p1=Student("Alice") p2=Teacher("Tom") p1.Pstr() p2.Pstr()
結果:
This is Student class method This is Person class method
python中的多重繼承
除了從一個父類繼承外,Python允許從多個父類繼承,稱為多重繼承。
例如:
class A(object): def __init__(self, a): print 'init A...' self.a = a class B(A): def __init__(self, a): super(B, self).__init__(a) print 'init B...' class C(A): def __init__(self, a): super(C, self).__init__(a) print 'init C...' class D(B, C): def __init__(self, a): super(D, self).__init__(a) print 'init D...'
像這樣,D 同時繼承自 B 和 C,也就是 D 擁有了 A、B、C 的全部功能。多重繼承通過 super()調用
__init__()方法時,A 雖然被繼承了兩次,但__init__()只調用一次:
運行測試代碼:
d=D('d')
結果:
init A...
init C...
init B...
init D...
類的定制
我們可以使用一些特殊的方法來定制我們自己定義的類
python中 __slots__
由於Python是動態語言,任何實例在運行期都可以動態地添加屬性。
如果要限制添加的屬性,例如,Student類只允許添加 name、gender和score 這3個屬性,就可以利用Python的一個特殊的__slots__來實現。
例如:
class Student(object): __slots__ = ('name', 'gender', 'score') def __init__(self, name, gender, score): self.name = name self.gender = gender self.score = score
進行操作:
>>> s = Student('Bob', 'male', 59) >>> s.name = 'Tim' # OK >>> s.score = 99 # OK >>> s.grade = 'A' Traceback (most recent call last): ... AttributeError: 'Student' object has no attribute 'grade'
__slots__的目的是限制當前類所能擁有的屬性,如果不需要添加任意動態的屬性,使用__slots__也能節省內存。
python中 __str__和__repr__對於print的改變
例如原本對於類中的實例直接打印:
class Student(object): def __init__(self, name): self.name = name p1=Student("Alice") print(p1)
結果:
<__main__.Student object at 0x000002672671EF98>
但是我們想讓print實例的時候直接打印類名與實例名我們就可以使用__str__:
class Student(object): def __init__(self, name): self.name = name def __str__(self): return 'Student: %s' % self.name p1=Student("Alice") print(p1)
結果:
Student: Alice
但是我們在命令交互行中直接輸入變量p1卻不是這樣:
>>>p1
<Student object at 0x0000023C8F331A58>
這是因為 Python 定義了__str__()和__repr__()兩種方法,__str__()用於顯示給用戶,而__repr__()
用於顯示給開發人員。
同理我們可以定義類的__repr__()例如直接:
__repr__ = __str__
這樣直接在命令交互中直接輸入變量p1也是同樣效果了
>>>p1
Student: Alice
python3中__cmp__方法已經不再使用
在python2中該方法可實現對於sorted()函數改變,不再贅述
python中 __len__對於len()的改變
如果一個類表現得像一個list,要獲取有多少個元素,就得用 len() 函數。
要讓 len() 函數工作正常,類必須提供一個特殊方法__len__(),它返回元素的個數。
例如,我們寫一個 Students 類,把名字傳進去:
class Students(object): def __init__(self, *args): self.names = args def __len__(self): return len(self.names) ss = Students('Bob', 'Alice', 'Tim') print (len(ss))
結果:
3
類的實例的數學運算
同以上特殊方法一樣,我們可以利用
__add__對符號 + 進行改變
__sub__對符號 -進行改變
__mul__對符號 * 進行改變
__div__對符號 / 進行改變
例如:
我們創建一個分數的類:
class Rational(object): def __init__(self, p, q): self.p = p self.q = q
我們想實現符號+直接運用在我們創建的實例上我們需要這樣寫代碼:
class Rational(object): def __init__(self, p, q): self.p = p self.q = q def __add__(self, r): return Rational(self.p * r.q + self.q * r.p, self.q * r.q) def __str__(self): return '%s/%s' % (self.p, self.q) __repr__ = __str__ r1 = Rational(1, 3) r2 = Rational(1, 2) print(r1+r2)
結果:
5/6
類的實例的類型轉換
如上,Rational類實現了有理數(分數)類的數學運算,但是,如果要把結果轉為 int 或 float 怎么辦?
我們對於整數和浮點數可以直接使用int()和float()函數直接轉換,但是我們肯定無法直接使用在我們所創建的類的實例上,因此我們需要使用__int__()和__float__()
例如__int__:
class Rational(object): def __init__(self, p, q): self.p = p self.q = q def __int__(self): return self.p//self.q #python3中//是整數結果
結果:
>>> print int(Rational(7, 2)) 3
python中 @property
對於Student類:
class Student(object): def __init__(self, name, score): self.name = name self.score = score
當我們想要修改一個 Student 的 scroe 屬性時,可以這么寫:
s = Student('Bob', 59) s.score = 1000
顯然,直接給屬性賦值無法檢查分數的有效性。
如果利用兩個方法:
class Student(object): def __init__(self, name, score): self.name = name self.__score = score def get_score(self): return self.__score def set_score(self, score): if score < 0 or score > 100: raise ValueError('invalid score') self.__score = score
這樣一來,s.set_score(1000) 就會報錯。
這種使用 get/set 方法來封裝對一個屬性的訪問在許多面向對象編程的語言中都很常見。但是寫 s.get_score() 和 s.set_score() 沒有直接寫 s.score 來得直接。
而在Python中,python支持高階函數,在函數式編程中我們介紹了裝飾器函數,可以用裝飾器函數@property。
把 get/set 方法“裝飾”成屬性調用:
class Student(object): def __init__(self, name, score): self.name = name self.__score = score @property def score(self): return self.__score @score.setter def score(self, score): if score < 0 or score > 100: raise ValueError('invalid score') self.__score = score
現在再設置score會怎么樣呢:
s = Student('Bob', 59) s.score = 60 print (s.score) try: s.score = 1000 except: print("Error")
結果:
60
Error
注意: 第一個score(self)是get方法,用@property裝飾,第二個score(self, score)是set方法,用@score.setter裝飾,@score.setter是前一個@property裝飾后的副產品。說明對 score 賦值實際調用的是 set方法。
如果沒有定義set方法即@score.setter,就不能對“屬性”賦值,這時,就可以創建一個只讀“屬性”。
python中 __call__
在Python中,函數其實是一個對象:
>>> f = abs >>> f.__name__ 'abs' >>> f(-123) 123
由於 f 可以被調用,所以,f 被稱為可調用對象。而所有的函數都是可調用對象。
一個類實例也可以變成一個可調用對象,只需要實現一個特殊方法__call__()。
我們把 Person 類變成一個可調用對象:
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender def __call__(self, friend): print ('My name is %s...' % self.name) print ('My friend is %s...' % friend)
現在可以對 Person 實例直接調用:
>>> p = Person('Bob', 'male') >>> p('Tim') My name is Bob... My friend is Tim...
單看 p('Tim') 你無法確定 p 是一個函數還是一個類實例,所以,在Python中,函數也是對象,對象和函數的區別並不顯著。