面向對象編程——Object Oriented Programming,簡稱OOP,是一種程序設計思想。OOP把對象做爲程序的基本單元,一個對象包含了數據和操做數據的函數。java
面向過程的程序設計把計算機程序視爲一系列的命令集合,即一組函數的順序執行。爲了簡化程序設計,面向過程把函數繼續切分爲子函數,即把大塊函數經過切割成小塊函數來下降系統的複雜度。python
而面向對象的程序設計把計算機程序視爲一組對象的集合,而每一個對象均可以接收其餘對象發過來的消息,並處理這些消息,計算機程序的執行就是一系列消息在各個對象之間傳遞。編程
在Python中,全部數據類型均可以視爲對象,固然也能夠自定義對象。自定義的對象數據類型就是面向對象中的類(Class)的概念。json
數據封裝、繼承和多態是面向對象的三大特色。服務器
在Python中,類經過 class 關鍵字定義。以 Person 爲例,定義一個Person類以下:網絡
class Person(object): pass
按照 Python 的編程習慣,類名以大寫字母開頭,緊接着是(object),表示該類是從哪一個類繼承下來的。類的繼承將在後面的章節講解,如今咱們只須要簡單地從object類繼承。多線程
有了Person類的定義,就能夠建立出具體的xiaoming、xiaohong等實例。建立實例使用 類名+(),相似函數調用的形式建立:ssh
xiaoming = Person()
xiaohong = Person()
注意:和其餘語言的最大不一樣是:建立實例不用new。函數式編程
雖然能夠經過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 進行排序。
sorted() 是高階函數,接受一個比較函數。
參考代碼:
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
雖然咱們能夠自由地給一個實例綁定各類屬性,可是,現實世界中,一種類型的實例應該擁有相同名字的屬性。例如,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
注意:當函數的參數不肯定時,可使用*args 和**kw(全稱**kwargs),*args 沒有key值,**kw有key值。傳形參時,**kw對應的參數裏=左邊的能夠當成key,=右邊的能夠當成value。
咱們能夠給一個實例綁定不少屬性,若是有些屬性不但願被外部訪問到怎麼辦?
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"雖然也能夠被外部訪問,可是,按照習慣,他們不該該被外部訪問。
關於 _xxx 和 __xxx ,通常來說,2個的區別是:
_xxx 能夠在子類中使用。(至關於其餘語言中的保護類型)
__xxx 不能夠在子類中使用。(至關於其餘語言中的私有類型)
而__xxx__做爲特殊屬性,在其餘語言中通常沒有。
類是模板,而實例則是根據類建立的對象。
綁定在一個實例上的屬性不會影響其餘實例,可是,類自己也是一個對象,若是在類上綁定一個屬性,則全部實例均可以訪問類的屬性,而且,全部實例訪問的類屬性都是同一個!也就是說,實例屬性每一個實例各自擁有,互相獨立,而類屬性有且只有一份。
定義類屬性能夠直接在 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 的實例。
因爲建立實例一定會調用__init__()方法,因此在這裏修改類屬性 count 很合適。
參考代碼:
class Person(object): count = 0 def __init__(self, name): Person.count = Person.count + 1 self.name = name p1 = Person('Bob') print Person.count # => 1 p2 = Person('Alice') print Person.count # => 2 p3 = Person('Tim') print Person.count # => 3
修改類屬性會致使全部實例訪問到的類屬性所有都受影響,可是,若是在實例變量上修改類屬性會發生什麼問題呢?
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
可見,千萬不要在實例上修改類屬性,它實際上並無修改類屬性,而是給實例綁定了一個實例屬性。
一個實例的私有屬性就是以__(兩個短橫)開頭的屬性,沒法被外部訪問,那這些屬性定義有什麼用?
雖然私有屬性沒法從外部訪問,可是,從類的內部是能夠訪問的。除了能夠定義實例的屬性外,還能夠定義實例的方法。
實例的方法就是在類中定義的函數,它的第一個參數永遠是 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
在實例方法內部,能夠訪問全部實例屬性,這樣,若是外部須要訪問私有屬性,能夠經過方法調用得到,這種數據封裝的形式除了能保護內部數據一致性外,還能夠簡化外部調用的難度。
例子:
請給 Person 類增長一個私有屬性 __score,表示分數,再增長一個實例方法 get_grade(),能根據 __score 的值分別返回 A-優秀, B-及格, C-不及格三檔。
注意:get_grade()是實例方法,第一個參數爲self。
class Person(object): def __init__(self, name, score): self.__name = name self.__score = score def get_grade(self): if self.__score >= 80: return 'A' if self.__score >= 60: return 'B' return 'C' p1 = Person('Bob', 90) p2 = Person('Alice', 65) p3 = Person('Tim', 48) print p1.get_grade() print p2.get_grade() print p3.get_grade()
咱們在 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() 把一個函數變爲一個方法:(相似於java的動態代理)
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()
p1.get_grade是屬性,只不過這裏的屬性是一個函數對象;
p1.get_grade()是方法,前面的p1就是調用這個方法的對象,即實例,整句來講就是實例方法。
和屬性相似,方法也分實例方法和類方法。
在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。
由於是在類上調用,而非實例上調用,所以類方法沒法得到任何實例變量,只能得到類的引用。
例子:
若是將類屬性 count 改成私有屬性__count,則外部沒法讀取__score,但能夠經過一個類方法獲取,請編寫類方法得到__count值。
注意:類方法須要添加 @classmethod
類必定要有__init__()方法
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()
老是從某個類中繼承,若沒有明顯的類繼承關係,那就是繼承於object類:
class Myclass(object): pass
不要忘記在子類的構造函數中調用父類的構造函數:
def __init__(self,args): super(SubClass,self).__init__(args) pass
若是已經定義了Person類,須要定義新的Student和Teacher類時,能夠直接從Person類繼承:
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender
定義Student類時,只須要把額外的屬性加上,例如score:
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__()中將隱式傳遞,不須要寫出(也不能寫)。
函數isinstance()能夠判斷一個變量的類型,既能夠用在Python內置的數據類型如str、list、dict,也能夠用在咱們自定義的類,它們本質上都是數據類型。
假設有以下的 Person、Student 和 Teacher 的定義及繼承關係以下:
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 class Teacher(Person): def __init__(self, name, gender, course): super(Teacher, self).__init__(name, gender) self.course = course p = Person('Tim', 'Male') s = Student('Bob', 'Male', 88) t = Teacher('Alice', 'Female', 'English')
當咱們拿到變量 p、s、t 時,可使用 isinstance 判斷類型:
>>> isinstance(p, Person) True # p是Person類型 >>> isinstance(p, Student) False # p不是Student類型 >>> isinstance(p, Teacher) False # p不是Teacher類型
這說明在繼承鏈上,一個父類的實例不能是子類類型,由於子類比父類多了一些屬性和方法。
咱們再考察 s :
>>> isinstance(s, Person) True # s是Person類型 >>> isinstance(s, Student) True # s是Student類型 >>> isinstance(s, Teacher) False # s不是Teacher類型
s 是Student類型,不是Teacher類型,這很容易理解。可是,s 也是Person類型,由於Student繼承自Person,雖然它比Person多了一些屬性和方法,可是,把 s 當作Person的實例也是能夠的。
這說明在一條繼承鏈上,一個實例能夠當作它自己的類型,也能夠當作它父類的類型。
類具備繼承關係,而且子類類型能夠向上轉型看作父類類型,若是咱們從 Person 派生出 Student和Teacher ,並都寫了一個 whoAmI() 方法:
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender def whoAmI(self): return 'I am a Person, my name is %s' % self.name class Student(Person): def __init__(self, name, gender, score): super(Student, self).__init__(name, gender) self.score = score def whoAmI(self): return 'I am a Student, my name is %s' % self.name class Teacher(Person): def __init__(self, name, gender, course): super(Teacher, self).__init__(name, gender) self.course = course def whoAmI(self): return 'I am a Teacher, my name is %s' % self.name
在一個函數中,若是咱們接收一個變量 x,則不管該 x 是 Person、Student仍是 Teacher,均可以正確打印出結果:
def who_am_i(x): print x.whoAmI() p = Person('Tim', 'Male') s = Student('Bob', 'Male', 88) t = Teacher('Alice', 'Female', 'English') who_am_i(p) who_am_i(s) who_am_i(t)
運行結果:
I am a Person, my name is Tim I am a Student, my name is Bob I am a Teacher, my name is Alice
這種行爲稱爲多態。也就是說,方法調用將做用在 x 的實際類型上。s 是Student類型,它實際上擁有本身的 whoAmI()方法以及從 Person繼承的 whoAmI方法,但調用 s.whoAmI()老是先查找它自身的定義,若是沒有定義,則順着繼承鏈向上查找,直到在某個父類中找到爲止。
因爲Python是動態語言,因此,傳遞給函數 who_am_i(x)的參數 x 不必定是 Person 或 Person 的子類型。任何數據類型的實例均可以,只要它有一個whoAmI()的方法便可:
class Book(object): def whoAmI(self): return 'I am a book'
這是動態語言和靜態語言(例如Java)最大的差異之一。動態語言調用實例方法,不檢查類型,只要方法存在,參數正確,就能夠調用。
例子:
ython提供了open()函數來打開一個磁盤文件,並返回 File 對象。File對象有一個read()方法能夠讀取文件內容:
例如,從文件讀取內容並解析爲JSON結果:
import json f = open('/path/to/file.json', 'r') print json.load(f)
因爲Python的動態特性,json.load()並不必定要從一個File對象讀取內容。任何對象,只要有read()方法,就稱爲File-like Object,均可以傳給json.load()。
請嘗試編寫一個File-like Object,把一個字符串 r'["Tim", "Bob", "Alice"]'包裝成 File-like Object 並由 json.load() 解析。
只要爲Students類加上 read()方法,就變成了一個File-like Object。
參考代碼:
import json class Students(object): def read(self): return r'["Tim", "Bob", "Alice"]' s = Students() print json.load(s)
除了從一個父類繼承外,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的網絡服務器有TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer,而服務器運行模式有 多進程ForkingMixin 和 多線程ThreadingMixin兩種。
要建立多進程模式的 TCPServer:
class MyTCPServer(TCPServer, ForkingMixin) pass
要建立多線程模式的 UDPServer:
class MyUDPServer(UDPServer, ThreadingMixin): pass
若是沒有多重繼承,要實現上述全部可能的組合須要 4x2=8 個子類。
拿到一個變量,除了用 isinstance() 判斷它是不是某種類型的實例外,還有沒有別的方法獲取到更多的信息呢?
例如,已有定義:
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 def whoAmI(self): return 'I am a Student, my name is %s' % self.name
首先能夠用 type() 函數獲取變量的類型,它返回一個 Type 對象:
>>> type(123) <type 'int'> >>> s = Student('Bob', 'Male', 88) >>> type(s) <class '__main__.Student'>
其次,能夠用 dir() 函數獲取變量的全部屬性:
>>> dir(123) # 整數也有不少屬性... ['__abs__', '__add__', '__and__', '__class__', '__cmp__', ...] >>> dir(s) ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'gender', 'name', 'score', 'whoAmI']
對於實例變量,dir()返回全部實例屬性,包括`__class__`這類有特殊意義的屬性。注意到方法`whoAmI`也是 s 的一個屬性。
如何去掉`__xxx__`這類的特殊屬性,只保留咱們本身定義的屬性?回顧一下filter()函數的用法。
dir()返回的屬性是字符串列表,若是已知一個屬性名稱,要獲取或者設置對象的屬性,就須要用 getattr() 和 setattr( )函數了:
>>> getattr(s, 'name') # 獲取name屬性 'Bob' >>> setattr(s, 'name', 'Adam') # 設置新的name屬性 >>> s.name 'Adam' >>> getattr(s, 'age') # 獲取age屬性,可是屬性不存在,報錯: Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'age' >>> getattr(s, 'age', 20) # 獲取age屬性,若是屬性不存在,就返回默認值20: 20
例子:
對於Person類的定義:
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender
但願除了 name和gender 外,能夠提供任意額外的關鍵字參數,並綁定到實例,請修改 Person 的 __init__()定 義,完成該功能。
傳入**kw 便可傳入任意數量的參數,並經過 setattr() 綁定屬性。
參考代碼:
class Person(object): def __init__(self, name, gender, **kw): self.name = name self.gender = gender for k, v in kw.iteritems(): setattr(self, k, v) p = Person('Bob', 'Male', age=18, course='Python') print p.age print p.course
若是要把一個類的實例變成 str,就須要實現特殊方法__str__():
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender def __str__(self): return '(Person: %s, %s)' % (self.name, self.gender)
如今,在交互式命令行下用 print 試試:
>>> p = Person('Bob', 'male') >>> print p (Person: Bob, male)
可是,若是直接敲變量 p:
>>> p
<main.Person object at 0x10c941890>
彷佛__str__() 不會被調用。
由於 Python 定義了__str__()和__repr__()兩種方法,__str__()用於顯示給用戶,而__repr__()用於顯示給開發人員。
有一個偷懶的定義__repr__的方法:
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender def __str__(self): return '(Person: %s, %s)' % (self.name, self.gender) __repr__ = __str__
對 int、str 等內置數據類型排序時,Python的 sorted() 按照默認的比較函數 cmp 排序,可是,若是對一組 Student 類的實例排序時,就必須提供咱們本身的特殊方法 __cmp__():
class Student(object): def __init__(self, name, score): self.name = name self.score = score def __str__(self): return '(%s: %s)' % (self.name, self.score) __repr__ = __str__ def __cmp__(self, s): if self.name < s.name: return -1 elif self.name > s.name: return 1 else: return 0
上述 Student 類實現了__cmp__()方法,__cmp__用實例自身self和傳入的實例 s 進行比較,若是 self 應該排在前面,就返回 -1,若是 s 應該排在前面,就返回1,若是二者至關,返回 0。
Student類實現了按name進行排序:
>>> L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 77)] >>> print sorted(L) [(Alice: 77), (Bob: 88), (Tim: 99)]
注意: 若是list不只僅包含 Student 類,則 __cmp__ 可能會報錯:
L = [Student('Tim', 99), Student('Bob', 88), 100, 'Hello'] print sorted(L)
解決:能夠寫一個filter函數:
def isStudent(x): if isinstance(x,Student): return True else: return False
若是一個類表現得像一個list,要獲取有多少個元素,就得用 len() 函數。要讓 len() 函數工做正常,類必須提供一個特殊方法__len__(),它返回元素的個數。
例如,咱們寫一個 Students 類,把名字傳進去:
class Students(object): def __init__(self, *args): self.names = args def __len__(self): return len(self.names)
只要正確實現了__len__()方法,就能夠用len()函數返回Students實例的「長度」:
>>> ss = Students('Bob', 'Alice', 'Tim') >>> print len(ss) 3
Python 提供的基本數據類型 int、float 能夠作整數和浮點的四則運算以及乘方等運算可是,四則運算不侷限於int和float,還能夠是有理數、矩陣等。
要表示有理數,能夠用一個Rational類來表示:
class Rational(object): def __init__(self, p, q): self.p = p self.q = q
p、q 都是整數,表示有理數 p/q。
若是要讓Rational進行+運算,須要正確實現__add__:
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類雖然能夠作加法,但沒法作減法、乘方和除法,請繼續完善Rational類,實現四則運算。
若是運算結果是 6/8,在顯示的時候須要歸約到最簡形式3/4。
參考代碼:
def gcd(a, b): if b == 0: return a return gcd(b, a % b) 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 __sub__(self, r): return Rational(self.p * r.q - self.q * r.p, self.q * r.q) def __mul__(self, r): return Rational(self.p * r.p, self.q * r.q) def __div__(self, r): return Rational(self.p * r.q, self.q * r.p) def __str__(self): g = gcd(self.p, self.q) return '%s/%s' % (self.p / g, self.q / g) __repr__ = __str__ r1 = Rational(1, 2) r2 = Rational(1, 4) print r1 + r2 print r1 - r2 print r1 * r2 print r1 / r2
Rational類實現了有理數運算,可是,若是要把結果轉爲 int 或 float 怎麼辦?
考察整數和浮點數的轉換:
>>> int(12.34) 12 >>> float(12) 12.0
若是要把 Rational 轉爲 int,應該使用:
r = Rational(12, 5)
n = int(r)
要讓int()函數正常工做,只須要實現特殊方法__int__():(注意是int不是init)
class Rational(object): def __init__(self, p, q): self.p = p self.q = q def __int__(self): return self.p // self.q
結果以下:
>>> print int(Rational(7, 2)) 3 >>> print int(Rational(1, 3)) 0
同理,要讓float()函數正常工做,只須要實現特殊方法__float__()。
繼續完善Rational,使之能夠轉型爲float。
將self.p轉型爲float類型,再做除法就能夠獲得float:
float(self.p) / self.q
參考代碼:
class Rational(object): def __init__(self, p, q): self.p = p self.q = q def __int__(self): return self.p // self.q def __float__(self): return float(self.p) / self.q print float(Rational(7, 2)) print float(Rational(1, 3))
注意:python中/表示浮點數,//表示整除,另外還有導入from __future__ import division,要不會報錯
考察 Student 類:
class Student(object): def __init__(self, name, score): self.name = name self.score = score
當咱們想要修改一個 Student 的 scroe 屬性時,能夠這麼寫:
s = Student('Bob', 59) s.score = 60
可是也能夠這麼寫:
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支持高階函數,在函數式編程中咱們介紹了裝飾器函數,能夠用裝飾器函數把 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(self)是get方法,用@property裝飾,第二個score(self, score)是set方法,用@score.setter裝飾,@score.setter是前一個@property裝飾後的副產品。
如今,就能夠像使用屬性同樣設置score了:
>>> s = Student('Bob', 59) >>> s.score = 60 >>> print s.score 60 >>> s.score = 1000 Traceback (most recent call last): ... ValueError: invalid score
說明對 score 賦值實際調用的是 set方法。
例子:
若是沒有定義set方法,就不能對「屬性」賦值,這時,就能夠建立一個只讀「屬性」。
請給Student類加一個grade屬性,根據 score 計算 A(>=80)、B、C(<60)。
用 @property 修飾 grade 的 get 方法便可實現只讀屬性。
參考代碼:
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 @property def grade(self): if self.score < 60: return 'C' if self.score < 80: return 'B' return 'A' s = Student('Bob', 59) print s.grade s.score = 60 print s.grade s.score = 99 print s.grade
注意:
@property---這是關鍵字,固定格式,能讓方法當「屬性」用。
@score.setter---前面的"score"是@property緊跟的下面定義的那個方法的名字,"setter"是關鍵字,這種「@+方法名字+點+setter」是個固定格式與@property搭配使用。
因爲Python是動態語言,任何實例在運行期均可以動態地添加屬性。
若是要限制添加的屬性,例如,Student類只容許添加 name、gender和score 這3個屬性,就能夠利用Python的一個特殊的__slots__來實現。
顧名思義,__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__也能節省內存。
例子:
假設Person類經過__slots__定義了name和gender,請在派生類Student中經過__slots__繼續添加score的定義,使Student類能夠實現name、gender和score 3個屬性。
Student類的__slots__只須要包含Person類不包含的score屬性便可。
參考代碼:
class Person(object): __slots__ = ('name', 'gender') def __init__(self, name, gender): self.name = name self.gender = gender class Student(Person): __slots__ = ('score',) def __init__(self, name, gender, score): super(Student, self).__init__(name, gender) self.score = score s = Student('Bob', 'male', 59) s.name = 'Tim' s.score = 99 print s.score
在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中,函數也是對象,對象和函數的區別並不顯著。
本篇博客參考:
慕課網課程:python進階
廖雪峯老師官網:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000