Python自動化開發學習6

wyfem發表於2021-09-09

引子

假設我們要在我們的程式裡表示狗,狗有如下屬性:名字、品種、顏色。那麼可以先定義一個模板,然後呼叫這個模板生成各種狗。

def dog(name,d_type,color):    data = {        'name':name,        'd_type':d_type,        'color':color}    return datad1 = dog('小七','拉布拉多','黃')d2 = dog('旺財','中華田野犬','黃')

上面用函式作為模板來定義資料確實很方便。然後我們要讓狗叫,那麼還需要在寫一個程式,簡單點,比如是這樣的

def dog(name,d_type,color):    data = {        'name':name,        'd_type':d_type,        'color':color}    return datad1 = dog('小七','拉布拉多','黃')d2 = dog('旺財','中華田野犬','黃')def bark(d):    print("dog %s:Wang~Wang~Wang~~~"%d['name'])bark(d2)

貌似也很完美,但是如果這是bark不小心引用了別的引數,比如一隻貓,那麼問題就來了。

def dog(name,d_type,color):    data = {        'name':name,        'd_type':d_type,        'color':color}    return datad1 = dog('小七','拉布拉多','黃')d2 = dog('旺財','中華田野犬','黃')def cat(name,c_type,color):    data = {        'name':name,        'c_type':c_type,        'color':color}    return datac1 = cat('比格沃斯','波斯貓','白')  # 在定義一個貓def bark(d):    print("dog %s:Wang~Wang~Wang~~~"%d['name'])def miaow(c):    print("cat %s:Miao~Miao~Miaow~~~"%c['name'])bark(c1)  # 這裡是不是亂套了miaow(d1)

所以這裡的問題就是,我們希望bark只能呼叫狗,cat只能呼叫貓。當然可以加上if判斷,但是很low。於是可以改成這樣:

def dog(name,d_type,color):    def bark():        print("dog %s:Wang~Wang~Wang~~~"%name)    data = {        'name':name,        'd_type':d_type,        'color':color,        'action':bark}  # 把動作的函式寫到狗的函式里,把函式名作為返回字典裡的一個元素    return datad1 = dog('小七','拉布拉多','黃')d2 = dog('旺財','中華田野犬','黃')def cat(name,c_type,color):    def miaow():        print("cat %s:Miao~Miao~Miaow~~~"%name)    data = {        'name':name,        'c_type':c_type,        'color':color,        'action':miaow}    return datac1 = cat('比格沃斯','波斯貓','白')d1['action']()  # 這樣呼叫狗模板裡的狗叫函式c1['action']()

把狗叫的函式也寫到狗的模板裡去,然後就實現了只能由狗呼叫狗叫的需求了。
引子講完了,上面並不是物件導向。上面的問題也不是很複雜,但是如果問題再複雜可能就解決不下去了,我們需要物件導向來解決這類問題。

程式導向 VS 物件導向

程式設計正規化

程式設計是程式設計師用特定的語法+資料結構+演算法組成的程式碼來告訴計算機如何執行任務的過程,實現一個任務的方式有很多種不同的方式,對這些不同的程式設計方式的特點進行歸納總結得出來的程式設計方式類別,即為程式設計正規化。兩種最重要的程式設計正規化分別是程式導向程式設計和麵向物件程式設計,然後還有一個函數語言程式設計。

程式導向程式設計(Procedural Programming)

程式導向程式設計,就是程式從上到下一步步執行,一步步從上到下,從頭到尾的解決問題。基本設計思路就是程式一開始是要著手解決一個大的問題,然後把一個大問題分解成很多個小問題或子過程,這些子過程再執行的過程再繼續分解直到小問題足夠簡單到可以在一個小步驟範圍內解決。
這樣做的問題也是顯而易見的,就是如果你要對程式進行修改,對你修改的那部分有依賴的各個部分你都也要跟著修改,隨著程式越來越大,這種程式設計方式的維護難度會越來越高。
但是程式導向依然是有可取之處的,如果你只是寫一些簡單的指令碼,去做一些一次性任務,用程式導向的方式是極好的,但如果你要處理的任務是複雜的,且需要不斷迭代和維護的,那還是用物件導向最方便了。

物件導向程式設計(Object-Oriented Programming)

OOP程式設計是利用“類”和“物件”來建立各種模型來實現對真實世界的描述,使用物件導向程式設計的原因一方面是因為它可以使程式的維護和擴充套件變得更簡單,並且可以大大提高程式開發效率。另外,基於物件導向的程式可以使它人更加容易理解你的程式碼邏輯,從而使團隊開發變得更從容。

物件導向程式設計介紹

無論用什麼形式來程式設計,我們都要明確記住以下原則:

  1. 寫重複程式碼是非常不好的低階行為

  2. 你寫的程式碼需要經常變更

物件導向的核心特性

  1. Class 類

  2. Object 物件

  3. Encapsulation 封裝

  4. Inheritance 繼承

  5. Polymorphism 多型

Class 類 和 Object 物件

:一個類即是對一類擁有相同屬性的物件的抽象、藍圖、原型。在類中定義了這些物件的都具備的屬性(variables(data))、共同的方法。
物件:一個物件即是一個類的例項化後例項,一個類必須經過例項化後方可在程式中呼叫,一個類可以例項化多個物件,每個物件亦可以有不同的屬性。
首先要定義類,然後將類例項化,最後透過這個例項來呼叫類中的功能
這裡類名要用大駝峰規範來命名

# class是定義類,Dog是類名,括號中的object暫時知道是必填就好了class Dog(object):    print("Hello , I am a dog.")# 上面已經定義好了類,下面來將類例項化d = Dog()  # 但是這裡會把print列印出來

我們不希望在例項化的時候就把print列印出來,而是要在呼叫類中的功能的時候再列印,那麼上面的類還得修改一下。

class Dog(object):    # 定義一個函式,把print寫到函式里,避免被直接呼叫執行    def sayhi(self):        print("Hello , I am a dog.")d = Dog()  # 這是這裡就不會直接列印了d.sayhi()  # 這樣來呼叫執行類裡的sayhi

d = Dog() 這步叫例項化。先去例項化,然後 d.sayhi() 再去呼叫它的功能
上面的例子我們沒有傳入引數,現在把引子裡的例子改成類,傳入屬性:

class Dog(object):    # 引數要寫在__init__這個函式里,這個叫建構函式或構造方法    def __init__(self,name,d_type,color):        self.name = name  # 將傳入的傳輸傳給self        self.type = d_type        self.color = color            # 下面的函式叫做類的方法    def bark(self):        print("dog %s: Wang~Wang~Wang~~~"%self.name)  # 這裡就可以呼叫self裡的值了d1 = Dog('旺財','中華田野犬','黃')  # 先例項化d1.bark()  # 然後呼叫功能

self,就是例項本身。你例項化時python會自動把這個例項本身透過self引數傳進去。上面例子中的self就是d1。再通俗一點,self就是呼叫當前方法的物件。
例項化後產生的物件,就叫例項,是這個類的例項。
d1 = Dog('旺財','中華田野犬','黃') 這個就是進行例項化,產生了Dog這個類的一個例項d1,而self就是這個例項本身
上面的__init__叫做建構函式,或者叫構造方法,也就是初始化的方法
上面的bark函式叫做類的方法,我們可以根據需要寫多個方法
我們寫一個給狗吃東西的函式food,把食物透過引數傳入
再寫一個給狗改名字的函式,這裡牽涉到修改物件的屬性值,也就是初始化的內容可以後期修改
再寫一個自報名字的函式,看看改名的效果:

class Dog(object):    def __init__(self,name,d_type,color):        self.name = name        self.type = d_type        self.color = color    def bark(self):        print("dog %s: Wang~Wang~Wang~~~"%self.name)    def eat(self,food):        print("%s 正在吃 %s"%(self.name,food))    def rename(self,new_name):        "給狗改名"        print("%s 改名為 %s"%(self.name,new_name))        self.name = new_name  # 這裡改變了物件的屬性    def say_name(self):        "報名字"        print("我的名字是:%s"%self.name)d1 = Dog('旺財','中華田野犬','黃')d1.bark()d1.eat("骨頭")  # 把骨頭傳給了foodd1.say_name()  # 現在的名字d1.rename("小黃")  # 改名d1.say_name()  # 再看看名字變了沒

總結:
類 ==》 例項化 ==》 例項(物件):類經過例項化後變成了例項也就是物件
__init__:建構函式
self.name = name :屬性,或者叫成員變數、欄位
def bark(self) :方法,或者叫動態屬性

Encapsulation 封裝

封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的資料和方法只讓可信的類或者物件操作,對不可信的進行資訊隱藏

私有屬性

之前使用的屬性並不是公有屬性,而是叫成員屬性,這些屬性是隻屬於它的物件的。公有屬性最後講。
私有屬性在類的外部不不可訪問的。我們來增加一個私有屬性hp,然後寫一個函式來操作hp這個私有屬性。定義私有屬性使用self.__屬性名

class Cat(object):    def __init__(self,name,c_type,color,hp=100):        self.name = name        self.c_type = c_type        self.color = color        self.__hp = hp  # 定義為私有屬性    def be_hit(self,damage):            "造成傷害,扣除hp值"        print("%s 受到了 %d點 傷害"%(self.name,damage))        print("當前hp:%d,傷害:%d,剩餘hp:%d"              %(self.__hp,damage,self.__hp-damage))        self.__hp -= damagec1 = Cat('比格沃斯','波斯貓','白')print("name:",c1.name)  # 公有屬性可以正常獲取到#print(c1.__hp)  # 這句會報錯,類的外部是獲取不到私有屬性的c1.be_hit(10)  # 這裡是透過類內部的方法列印的私有屬性的數值c1.be_hit(5)

既然可以透過內部方法訪問私有屬性,我們可以將私有屬性寫到函式里return,提供一個給外部訪問的方法,但是不能修改。另外其實也是有方法可以強制訪問的。

class Cat(object):    def __init__(self,name,c_type,color,hp=100):        self.name = name        self.c_type = c_type        self.color = color        self.__hp = hp    def be_hit(self,damage):        print("%s 受到了 %d點 傷害"%(self.name,damage))        print("當前hp:%d,傷害:%d,剩餘hp:%d"              %(self.__hp,damage,self.__hp-damage))        self.__hp -= damage    def get_hp(self):        "提供方法,返回私有屬性hp的值"        return self.__hpc1 = Cat('比格沃斯','波斯貓','白')print(c1.get_hp())  # 透過內部提供的方法來訪問私有屬性print(c1._Cat__hp)  # 其實也可以強制訪問到私有屬性,並這個是可以修改私有屬性值的

物件名._類名__私有屬性名 : 強制訪問私有屬性,可讀寫

公有屬性

公有屬性,所有屬於這個類的物件都可以訪問的屬性,才叫公有屬性。
之前的例子中,類Dog定義了2個物件d1 和 d2 ,但是d1裡的屬性是隻屬於d1的,無法透過d2來訪問,這些都是叫成員屬性
在類裡直接定義的屬性,既公有屬性。

class Dog(object):    called = "狗"  # 在類裡定義公有屬性    def __init__(self,name,d_type,color):        self.name = name        self.type = d_type        self.color = colord1 = Dog('旺財','中華田野犬','黃')d2 = Dog('小七','拉布拉多','黃')print(d1.called)print(d2.called)Dog.called = "犬"  # 透過類更改類公有屬性print(d1.called)  # 透過類修改,所有物件都會變print(d2.called)d1.called = "看門狗"  # 透過物件更改,其實這句是新建了d1裡的成員屬性# 同時有成員屬性和公有屬性,則使用成員屬性print(d1.called)  # 這裡列印的是剛才新建的成員屬性print(d2.called)  # 這裡還是原來的公有屬性Dog.called = "dog"  # 再透過類更改公有屬性print(d1.called)  # 這裡沒有變化,因為成員屬性還在,並且沒變print(d2.called)  # 這裡變化了,因為這裡只有公有屬性del d1.called  # 把成員屬性從記憶體中清楚print(d1.called)  # 現在全部是公有屬性值了print(d2.called)d1.called = "看門狗"d2.called = "導盲犬"  # 現在全部都有成員屬性了print(d1.called)print(d2.called)print(Dog.called)  # 直接透過類而不是物件獲取的一定是公有屬性

這裡公有屬性和成員屬性的情況和之前學的全域性變數和區域性變數是一樣的效果。
展開講一下函式的情況,類中def定義的所有函式我們也可以理解為是公有的。那麼也可以透過定義成員的方法來替換原來的公有的方法:

class Dog(object):    called = "狗"    def __init__(self,name,d_type,color):        self.name = name        self.type = d_type        self.color = color    def bark(self):        print("dog %s: Wang~Wang~Wang~~~"              %self.name)'''在類外面再定義一個函式,這裡無法使用self傳入變數了如果要傳入引數,那麼下面呼叫的時候也得帶引數傳入,這樣呼叫方法也變了'''def bark():    "自己再定義一個bark"    print("TEST")d1 = Dog('旺財','中華田野犬','黃')d2 = Dog('小七','拉布拉多','黃')d1.bark()  # 正常呼叫類中的方法d2.bark()d1.bark = bark  # 透過這個方法來實現給d1一個自己的成員方法d1.bark()  # 現在呼叫的不是公有方法,而是d1自己的成員方法d2.bark()del d1.bark  # 清除d1的bark方法後,d1有可以正常呼叫類的bark方法了d1.bark()d2.bark()

析構方法 和 構造方法

方法和函式:在類裡面定義的函式就是這個類的方法,所以方法和函式這兩個詞有時候會混用,主要看你是在描述什麼東西
構造方法:之前在為類傳入引數的時候用到了建構函式,建構函式其實就是在生成1個例項的時候自動執行的函式,所以透過建構函式我們可以實現在生成例項的時候自動把引數傳遞給self
析構方法:和構造方法差不多,就是在一個例項被銷燬的時候自動執行的函式

class test(object):    def __init__(self):  # 建構函式        print("init in the test")    def __del__(self):  # 解構函式        print("del in the test")input("準備例項化物件")obj = test()input("準備銷燬物件")del objinput("執行完畢")

把一些收尾的工作寫在解構函式里,在你銷燬這個物件的時候就自動執行,比如關閉所有的客戶連線、關閉所有開啟的檔案等等。具體怎麼用得到了以後用的時候才知道了。

Inheritance 繼承

繼承是指這樣一種能力:它可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴充套件。
透過繼承建立的新類稱為“子類”或“派生類”。
被繼承的類稱為“基類”、“父類”或“超類”。
繼承的過程,就是從一般到特殊的過程。
要實現繼承,可以透過“繼承”(Inheritance)和“組合”(Composition)來實現。
要同時繼承多個類有2種方法:多重繼承和多級繼承
繼承概念的實現方式主要有2種:實現繼承、介面繼承。

  1. 實現繼承是指使用基類的屬性和方法而無需額外編碼的能力

  2. 介面繼承是指僅使用屬性和方法的名稱,但是子類必須提供實現的能力(子類重構父類方法)

抽象類:僅定義將由子類建立的一般屬性和方法。
OOP開發正規化大致為:劃分物件→抽象類→將類組織成為層次化結構(繼承和合成) →用類與例項進行設計和實現幾個階段。
簡單的繼承例子:

class Human(object):    def talk(self):        print("Hello World")class Chinese(Human):  # 這樣就繼承了Human這個類    pass  # 什麼也不寫,但是我們有繼承啊h1 = Chinese()  # 例項化1個chinese的物件h1.talk()  # 雖然Chinaes沒有talk方法,但是繼承了Human的talk方法

子類也可以有自己的方法,還可能重構父類的方法:

class Human(object):    def talk(self):        print("Hello World")class Chinese(Human):  # 這樣就基礎了Human這個類    "這次我們來寫幾個方法"    def greatWall(self):  # 定義一個新的方法        print("長城長啊長")    def talk(self):  # 重構父類的方法        print("你好,世界")h1 = Chinese()  # 例項化1個chinese的物件h1.talk()  # 呼叫的是重構後的新方法h1.greatWall()  # 在子類中定義的新方法

再加上類的引數:
先只寫上父類的建構函式,子類不寫。子類就是完全繼承父類建構函式。

class Human(object):    def __init__(self,name,age):  # 父類裡有建構函式        self.name = name        self.age = age    def talk(self):        print("Hello World")class Chinese(Human):    pass  # 子類沒有建構函式,就直接繼承父類的建構函式#h1 = Chinese()  # 沒有引數會報錯,因為建構函式需要傳入2個引數h1 = Chinese("張三",33)  # 例項化的時候,需要根據建構函式的要求傳入引數h1.talk()

再來看子類的屬性,上面是完全繼承父類的屬性,那麼就不用寫建構函式。
也可以完全無視父類的屬性,那麼直接重構自己的建構函式就好了。
複雜一點情況,繼承父類的屬性,但是還要有自己的額外屬性。

class Human(object):    def __init__(self,name,age):  # 父類的建構函式        self.name = name        self.age = age    def talk(self):        print("Hello World")class Chinese(Human):    def __init__(self,name,age,kungfu):  # 先繼承,再重構        #Human.__init__(self,name,age)  # 呼叫父類的建構函式,實現繼承        super(Chinese,self).__init__(name,age)  # 和上面那句效果一樣,用這種寫法        self.kungfu = kungfu  # 再增加自己重構的屬性    def talk(self):  # 這裡順便把函式也重構了,但是我們要保留父類裡的部分        Human.talk(self)  # 方法也可以和屬性一樣,實現繼承和重構        print("你好,世界")h1 = Chinese("張三",33,"少林")  # 例項化的時候,需要根據建構函式的要求傳入引數h1.talk()print(h1.__dict__)  # 順便來看一下這個的作用

子類要繼承父類屬性並且有自己的特有屬性,需要先繼承,再重構。透過父類的名字呼叫執行父類的建構函式,實現繼承,然後可以在後面寫上自己需要重構的程式碼。
函式也是可以用這個方法來呼叫父類的方法,新增自己的程式碼,實現在父類的基礎上重構自己特有的部分
__dict__ 屬性:可以檢視物件所有的屬性和值,以字典的形式。

多繼承

class Chinese(Human):  這個是繼承的語法,括號中的變數可以傳入多個類,用逗號隔開就實現了多繼承。比如:
class Chinese(Human,Person):  上課說用的不多,也沒展開。所以暫時就知道這個語法就好了。
下面也是在多繼承的時候才會有區別的內容

新式類 和 經典類

python3裡已經沒有這個問題了,並且現在都是用的新式類的寫法。不過還是舉個例子說明一下繼承的順序,這個是多繼承的情況下會遇到的問題。
定義一個基類A,然後是A的兩個子類B和C。最後來個孫子類D,D要繼承B和C。每個類裡都定義一個屬性n,寫在構造方法裡

class A(object):    pass    def __init__(self):        self.n = "A"class B(A):    pass    def __init__(self):        self.n = "B"class C(A):    pass    def __init__(self):        self.n = "C"class D(B,C):    pass    #def __init__(self):        #self.n = "D"d1 = D()print(d1.n)

如果D有構造方法,那麼結果一定是D。然後依次將上面的構造方法也註釋掉,看看D的繼承順序。結果是B-C-A。這個叫廣度查詢。
經典類就不試了,要在python2裡才會有深度查詢的效果。在python3裡還是廣度查詢。

  • 新式類,廣度查詢 B-C-A

  • 經典類,深度查詢 B-A-C

語法上的區別,都用新式類就好了,經典類知道一下,看到的時候別不認識。
定義類的語法:
class A:  # 經典類寫法,python2裡和下面的寫法有區別。python3裡不必顯示宣告,這麼寫也是新式類了。
class A(object):  # 新式類寫法,我們就這麼寫
呼叫父類方法的語法:
Human.__init__(self,name,age)  # 經典類寫法,這個是子類建構函式里實現先繼承的那句程式碼
super(Chinese,self).__init__(name,age)  # 新式類寫法,我們就這麼寫
不單是建構函式,其他函式也一樣,儘量都super,不是多繼承的話兩個都一樣。但是絕對不要混用。

Polymorphism 多型

多型,簡單點說:"一個介面,多種實現",指一個基類中派生出了不同的子類,且每個子類在繼承了同樣的方法名的同時又對父類的方法做了不同的實現,這就是同一種事物表現出的多種形態。
多型允許將子類的物件當作父類的物件使用,某父型別的引用指向其子型別的物件,呼叫的方法是該子型別的方法。這裡引用和呼叫方法的程式碼編譯前就已經決定了,而引用所指向的物件可以在執行期間動態繫結
封裝可以隱藏實現細節,使得程式碼模組化;繼承可以擴充套件已存在的程式碼模組(類);它們的目的都是為了,程式碼重用。而多型則是為了實現另一個目的,介面重用。多型的作用,就是為了類在繼承和派生的時候,保證使用“家譜”中任一類的例項的某一屬性時的正確呼叫。
舉例:Pyhon 很多語法都是支援多型的,比如 len(),sorted(), 你給len傳字串就返回字串的長度,傳列表就返回列表長度。
講了那麼多,到底是要透過多型做什麼?就是要,透過父類調子類,python不直接支援,但是可以間接支援。

class People(object):  # 先定義一個基類    def talk(self):  # 基類的talk方法,我們不希望被呼叫,寫一個會丟擲錯誤的程式碼        "如果基類的這個方法被呼叫,就丟擲一個錯誤"        raise NotImplementedError("Subclass must implement abstract method")class Chinese(People):  # 這個是子類    def talk(self):  # 重構talk方法        print("你好,世界")class American(People):    def talk(self):        print("Hello World")# 如果呼叫了基類的方法,會根據raise裡定義的,丟擲一個錯誤。去掉下面的註釋測試一下#p1 = People()  # 例項化一個基類#p1.talk()  # 呼叫基類的talk方法# 例項化2個物件c1 = Chinese()a1 = American()# 透過子類呼叫自己的方法當然沒問題。要用多型就是要使用統一的介面來實現這2條命令c1.talk()a1.talk()# 多型是要用父類呼叫子類#People.talk(c1)  # 這樣是最好的,真正的直接實現多型的方法,但是Python不支援#People.talk(a1)# 間接支援多型的方法,定義一個函式作為統一的介面def People_talk(obj):    obj.talk()# 用新定義的介面,呼叫不同的子類,每次用的都是這個子類裡重構的那個方法People_talk(c1)  # 傳入一個c1物件,實際就是執行c1.talk()People_talk(a1)  # 傳入一個a1物件,實際就是執行a1.talk()

間接支援多型,新定義一個函式,用引數傳入一個物件。然後再函式中呼叫這個物件的一個方法。
主動丟擲一個錯誤,上面在基類裡使用了一個raise命令,可以實現主動觸發一個錯誤,可以定義這個錯誤的型別和訊息。這裡的作用就是驗證這個方法沒有被呼叫到,貌似一般都是用一句print來驗證的,這個也很高階。

補充

物件導向的應用場景

學了那麼多物件導向,但是什麼時候用呢?畢竟在python裡我們使用程式導向的方法也是一樣可以實現的。課上總結了3種場景,推薦使用物件導向的方法來實現,並且確實更好。

  1. 根據一個模板來建立某些東西的時候

  2. 縱向擴充套件

  3. 橫向擴容

第一條從前面引子開始就在舉例子了。後面兩個名詞用在這是我自己總結的。
縱向擴充套件,對一個物件有多個不同的操作,比如連線一個伺服器、執行一條命令、上傳一個檔案、斷開與伺服器的連線。把這種對同一個物件執行的不同的操作寫在一個類裡,每一種操作就是類裡的一個函式
橫向擴充套件,原本有很多個函式都需要傳公共的引數的時候,可以都寫到一個類裡。比如有很多個操作都需要和伺服器互動,那麼就都會需要地址、埠、密碼這些引數,然後不同的方法又需要不同的其他引數。每次定義函式以及之後呼叫函式都會重複的引用這幾個重複的引數。

# 程式導向定義3個函式,其中都會用到3個一樣的引數def f1(host,port,pwd,arg1):    passdef f2(host,port,pwd,arg1,arg2):    passdef f3(host,port,pwd,arg1,arg2,arg3):    pass# 呼叫的時候也要反覆的來引用這些引數f1(1,2,3,4)f2(1,2,3,4,5)f3(1,2,3,4,5,6)# 物件導向來做同樣的事情,重複的引數寫到構造方法裡class Foo(object):    def __init__(self,host,port,pwd):        self.host = host        self.port = port        self.pwd = pwd    def f1(arg1):        pass    def f2(arg1,arg2):        pass    def f3(arg1,arg2,arg3):        pass# 呼叫的時候先把重複的引數寫在一個物件裡,然後可以分別呼叫這個物件的不同的方法obj = Foo(1,2,3)obj.f1(4)obj.f2(4,5)obj.f3(4,5,6)

類中的其他方法

類中的函式我們叫方法,預設在類中定義的函式都是儲存在類中,要呼叫這個方法需要透過物件。叫做例項方法,就是必須是例項化之後才能使用的方法,之前都是這種例項方法。
靜態方法,就是一種普通函式,儲存在類中,可以透過類來呼叫。使用裝飾器@staticmethod定義靜態方法。

class Foo(object):    def f1(self):  # 這裡的self引數是必須的        print("test in f1")    @staticmethod  # 這個是靜態方法    def f2():  # 這裡的引數不是必須的了        print("test in f2")Foo.f2()  # 並沒有建立物件,直接透過類呼叫了靜態方法,類似函式了obj.f2()  # 當然由於有繼承,透過物件也能夠呼叫類中的靜態方法obj = Foo()obj.f1()

上面的例子中,f1是例項方法,f2就是靜態方法。
這裡的f1不要這麼用,因為這個方法裡並沒有用到物件裡面的任何東西。但是這麼寫,要呼叫f1必須得先例項化一個物件,但是其實並不需這個物件,而且還浪費空間。所以這種情況下,按照f2那樣定義成一個靜態方法會更好。
在別的純物件導向的語言裡,也是提供靜態方法的。透過這種靜態方法,可以讓我們直接就能執行這個方法了。
另外除了靜態方法和例項方法,還有一個類方法,這個貌似和靜態方法在python裡差不多,下面直接看看區別。

靜態方法 和 類方法的區別

類方法可以將類作為引數傳入,在繼承的時候,子類會優先呼叫子類的屬性和方法。
靜態方法,無法傳入類和物件,所以無論在哪個級別,永遠都是呼叫基類的屬性和方法

class Father(object):    test = 'This is in Father'    @classmethod    def test_classmethod(cls):        print(cls.test)  # 類方法,可以將類作為引數    @staticmethod    def test_staticmethod():        print(Father.test)  # 靜態方法,沒有引數傳入,只能用自己的類名,呼叫自己這個類的屬性class Son(Father):    test = "This is in Son"Father.test_classmethod()Father.test_staticmethod()Son.test_classmethod()  # 繼承後的子類的類方法的結果不同了Son.test_staticmethod()

類方法和靜態方法的作用就是可以透過類來呼叫類中的屬性和方法,而不需要先進行例項化。在還沒有例項化生成物件的時候,只能呼叫類中的這兩種方法。

類的組合關係

類與類之間除了可以是繼承關係,還可以有組合關係。比如有兩個類,學生和老師,那麼可以在這之上在定義一個學校成員類,把學生和老師公有的屬性和方法放在學校成員類裡,作為父類。而學生和老師就作為兩個子類繼承父類。這是是之前將的繼承,類於類之間是一種屬於的關係。
現在又有一個類,課程類。課程不屬於任何的人,但是老師有教授的課程,學生也有學習的課程。我們希望可以透過老師來獲得他所教授的課程,也就是獲得課程類的屬性和方法。所以課程和老師之間又需要有這某種關係,這是就需要用到組合關係。
假設一個老師只教一門課,先搞清楚組合關係,不考慮教多門課。
如果沒有組合關係,我們大概可以把課程的屬性作為老師的屬性,如下:

class Teacher(object):    def __init__(self,name,age,course_name,period,title):        "名字、年齡、課程名、課時、職稱"        self.name = name        self.age = age        self.course_name = course_name        self.period = period        self.title = title

當然我們還會有學生類,這會用到這些屬性,除了職稱。透過繼承關係我們可以把名字和年齡作為一個父類,讓老師和學生繼承,但是課程和課時就無法利用繼承關係實現程式碼的重複使用了。

class Course(object):    def __init__(self,name,period):        "課程名、課時"        self.name = name        self.period = periodclass People(object):    def __init__(self,name,age):        "名字、年齡"        self.name = name        self.age = ageclass Teacher(People):    def __init__(self,name,age,title):        "名字、年齡、職稱"        super(Teacher,self).__init__(name,age)        self.title = titleclass Student(People):    def __init__(self,name,age):        super(Student,self).__init__(name,age)t1 = Teacher("Bob",32,"教授")print(t1.name,t1.age,t1.title)

上面只是把繼承關係寫好了,單獨把課程定義為了一個類,但是並沒有把課程組合和別的類組合起來。
方法一:先把課程例項化,將例項化後的課程作為People類的一個屬性傳入

class Course(object):    def __init__(self,name,period):        "課程名、課時"        self.name = name        self.period = periodclass People(object):    def __init__(self,name,age,course):        "名字、年齡、課程"        self.name = name        self.age = age        self.course = courseclass Teacher(People):    def __init__(self,name,age,course,title):        super(Teacher,self).__init__(name,age,course)        self.title = title  # 教師比學生多一個職稱屬性class Student(People):    def __init__(self,name,age,course):        super(Student,self).__init__(name,age,course)c1 = Course("python",360)  # 先例項化一個課程t1 = Teacher("Bob",32,c1,"教授")  # 課程是教師的一個屬性print(t1.course.name,t1.course.period)  # 透過教師例項來呼叫課程類的屬性

方法二:不用例項化課程,而是在People類的構造方法裡完成課程類的例項化。這樣需要在例項化教師的時候,將課程類的屬性一起傳入。

class Course(object):    def __init__(self,name,period):        "課程名、課時"        self.name = name        self.period = periodclass People(object):    def __init__(self,name,age,course_name,period):        self.name = name        self.age = age        self.course = Course(course_name,period)  # 在建構函式里完成例項化class Teacher(People):    def __init__(self,name,age,course_name,period,title):        super(Teacher,self).__init__(name,age,course_name,period)        self.title = titleclass Student(People):    def __init__(self,name,age,course_name,period):        super(Student,self).__init__(name,age,course)t1 = Teacher("Bob",32,"python",360,"教授")  # 需要將課程類的屬性在例項化的時候一起傳入print(t1.course.name,t1.course.period)  # 呼叫方法都是一樣的

組合同樣也是為了減少重複程式碼。把另一個類的例項作為自己的屬性完成例項化(方法一),或者在例項化的時候同時完成了另一個類的例項化來用作自己的屬性(方法二)。之後就可以透過一個類來呼叫被組合的類的屬性和方法了。
上面只舉例了呼叫一個被組合的類的屬性,要使用方法也是一樣的。另外例子中老師和學生都是會上多門課的,這裡只要引入陣列的概念。這是之前學過的概念,關鍵是要把所有的知識點融會貫通。

作業:選課系統

角色:學校、講師、學員、課程
要求:

  1. 建立北京、上海 2 所學校

  2. 建立linux , python , go 3個課程 , linuxpy 在北京開, go 在上海開

  3. 課程包含,週期,價格,透過學校建立課程

  4. 透過學校建立班級, 班級關聯課程、講師

  5. 建立學員時,選擇學校,關聯班級

  6. 建立講師角色時要關聯學校,

  7. 提供至少兩個角色介面
    7.1 學員檢視, 可以註冊, 交學費, 選擇班級,
    7.2 講師檢視, 講師可管理自己的班級, 上課時選擇班級, 檢視班級學員列表 , 修改所管理的學員的成績
    7.3 管理檢視,建立講師, 建立班級,建立課程

  8. 上面的操作產生的資料都透過pickle序列化儲存到檔案裡

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2894/viewspace-2802502/,如需轉載,請註明出處,否則將追究法律責任。

相關文章