天天看點

Python自動化開發學習7經典類 和 新式類靜态方法、類方法屬性方法類的特殊成員方法建立元類反射動态導入子產品異常處理Socket子產品作業

<code>class A</code> 經典類寫法,查找方式深度優先

<code>class A(object)</code> 新式類寫法,查找方式廣度優先

上面是python2的文法,python3裡可能已經沒有經典類了。不管有沒有,都用形式類來寫就對了。

上面都是上節講的内容,再講一下構造函數的問題。

<code>Father.__init__(self,name,age)</code> 這個是經典類的構造函數寫法,把父類的名字寫在前面,但是問題是若幹是多繼承呢。這一句顯然隻繼承了一個父類。其他父類的屬性就沒有繼承到了。那麼就是有幾個父類要寫幾個構造函數了。

<code>super(Son,self).__init__(name,age)</code> # super就一次能把所有父類的屬性繼承到了

多繼承的情況可能用不到,或者也可以用其他方法來替代,比如組合。暫時就掌握這麼多了

靜态方法和類方法上一節已經講過了。

靜态方法,通過@staticmethod裝飾,可以不用傳入參數,無法動态的調用任何執行個體或者類的屬性和方法

類方法,通過@classmethod裝飾,必須傳入一個類作為參數,可以動态的調用傳入的類的屬性和方法

屬性方法,通過@property裝飾,把一個方法變成一個屬性。調用的時候是一個屬性,定義的時候是用方法了定義的。

看着好像有點用,但是并沒有什麼實際用處。如果這個屬性值是需要一系列的運算後才獲得的,那麼我可以把為了擷取到這個屬性值的操作都寫在這個屬性方法裡。但是在類的外部隻要把它當做一個屬性來調用就好了。

比如一個人,我隻需要一個姓,一個名,當需要用到全名的時候,我隻有通過姓和名拼接後就可以獲得全名

上面的情況,調用full_name就很整齊,和其他兩個一樣都是通過屬性調用的。當然其實在構造函數裡寫個self.full_name也是一樣能實作的,隻怪這個算法太簡單。如果需要幾行代碼的話,隻能另外寫一個函數來計算并傳回,然後self.full_name指派那個函數的傳回值,這一通操作之後也是一樣的效果。權且先當到一個實作方法吧

下面是老師的例子:

屬性方法還沒完,既然是方法,那麼就會有需要傳參數,可是調用的時候又是屬性,那麼就沒有()就沒地方寫參數了。不過既然是個屬性,那麼我們可以給它指派,通過指派來傳參數。

雖然老師是這麼講的,但是或許該這麼了解。這個方法現在就是一個屬性,擷取屬性時用的是上面的方法,然後我們還可以給屬性指派(設定屬性),删除屬性(del 這個屬性)。下面的例子就是分别寫三個方法對應擷取屬性時使用的方法、設定屬性時使用的方法、删除屬性時使用的方法。普通的屬性的設定和删除python有自己的方法,這裡我們就通過屬性方法自定義了自己的屬性在上面3個操作的時候具體執行什麼

上面的3個裝飾器分别是擷取屬性是使用的方法,設定屬性時使用的方法、删除屬性時使用的方法。這裡删除屬性時一般如果就是要删除這個屬性,那麼就在方法裡寫一個del。不過這裡我們想讓他做點别的而不是删除,那麼也是可以的。不過既然占用了删除屬性的方法,那麼就沒辦法主動删除這個屬性喽。(好像一般也不會去主動删除掉哪個屬性)

下面的例子用這3個裝飾器來重構了name這個屬性

其實上面的代碼并沒有意義,和下面的一樣,

但是現在我們可以在我們自己重構的屬性方法裡加入各種代碼,來實作我們其他的需求。舉個例子,比如檢查屬性類型:

下面的内置函數是之前講内置函數時跳過的,因為是一個類裡使用的内置函數。但是其實這裡還是要忽略。是以了解一下,然後忘記它。

有一個同名的内置方法property(fget=None, fset=None, fdel=None, doc=None)。前3個參數就和上面裝飾器的是一樣的,分别是擷取屬性的方法、設定屬性的方法、删除屬性的方法。上面的函數可以改成這樣:

效果一樣,但還是用裝飾器來寫,不過裝飾器是隻有在新式類中才有的。property()可以忘記它,用這個low了,具體啥原因不清楚,大概是要多起3個函數名?或者就結構不清晰,分成了獨立的4部分,不像裝飾器是綁在函數前面的。

這個并不是隻屬于類的方法,對于函數和子產品同樣有效。我們寫函數或類的時候,應該在第一行以字元串的格式做說明。這裡用字元串而不是注釋的意義就在于,通過<code>__doc__</code>是可以擷取到的

傳回一個字典,key是屬性名,value是屬性值

公有屬性,列印對象的時候是擷取不到的,因為記錄在類的屬性裡

列印類的所有屬性會看到一些特殊屬性,但是不是全部,比如<code>__call__</code>是沒有的,但是如果定義了這個方法,就會顯示出來

是以真的要用這個方法列印出所有屬性,需要把類和對象的屬性都找出來,去掉其中的特殊屬性。類和對象中都有的屬性,隻要對象的。

如果沒有<code>__str__</code>方法,則預設列印記憶體位址

這裡的3個方法和屬性方法比較類似了,通過這3個方法可以把對象當做是字典來操作了。或者說自定義一個字典。

或許還有自定義清單的方法,上課說python3裡沒了,就沒講。

元類是用來建立類的類。我們建立類是通過元類來建立的。通過了解元類建立類的過程,可以對類有更深的了解。當然不了解也不影響我們使用類和用面向對象的方法程式設計。

先學習2個基礎一點的知識,然後在看看元類是什麼,元類是如何建立類的。

建立執行個體我們之前都不知道new的存在,但是執行個體是通過new方法來建立的。先來看個例子,我們重構new方法

運作結果,隻有new方法被執行了,構造方法并沒有被執行。當然沒有執行構造方法也就不需要name參數,是以這裡<code>Foo()</code>并沒有報錯。按之前了解的,構造方法是在執行個體化的時候自動被執行的,這裡我們寫了new方法後就不自動執行了。因為這裡我們重構了new方法,原本是通過new方法來調用執行構造函數的。另外,構造方法在執行個體化的時候自動執行并沒有錯,其實這裡我們還沒有完成執行個體化,因為new沒有調用構造方法,沒有做執行個體化的操作。是以new函數裡應該有這麼一句,如下

上面的結果看,先執行的new方法,再執行構造方法。執行個體是通過new來建立的。如果你想定制你的類,在執行個體化之前定制,需要使用new方法。說到繼承,這裡的寫法和構造方法是一樣的,可以先了解經典類的寫法,比較直覺。新式類用super的寫法參考之前的構造函數改一下也就出來了。

new方法必須要有傳回值,傳回執行個體化出來的執行個體。使用經典類寫法指定的話,可以return父類的new方法出來的執行個體,也可以直接将object的new出來的執行個體傳回。但是這個傳回值和構造并看不出有什麼關系,為什麼就觸發了構造方法呢?後面會繼續講。

現在我們已經知道了,類是通過自己的new方法來建立執行個體的。

先看一個簡單的類

我們列印了對象f1的類型,f1對象是由Foo建立。在python中一切皆對象,那麼Foo這個對象我們從輸出結果看,應該是由type建立的。是以我們可以用tpye來建立Foo這個類

上面就是用type建立類的方法,效果一模一樣。這裡type有三個參數

type(object_or_name, bases, dict)

object :第一個參數可以是另外一個對象,那麼新建立的對象就是這object這個對象同一類型

name :第一個參數也可以是個名字,那麼name就是這個新類型的名字

bases :第二個參數是目前類的基類,可以為空,那麼就是一個經典類。我們這裡是按新式類來基礎object。這個參數值接收元組,是以這裡要這麼寫<code>(object,)</code>,這樣就是一個隻有一個元素的元組,沒有逗号的話,會被作為一個type類型。

dict :第三個參數是一個字典,就是這個類的所有成員。公有屬性以及方法

這裡type也是一個類,叫元類

現在我們已經知道了,類是通過type類來建立的。

類中有一個 <code>__metaclass__</code> 屬性,表示該類是由誰來執行個體化建立的。之前我們預設建立的基類,都是由type元類來執行個體化建立的。

<code>__metaclass__</code> 屬性是python2中的講法,在python3中已經變成了metaclass,已經不是一個屬性了,但是作用沒變。

上面的鋪墊,主要是這2點:

執行個體是通過類的new方法來建立的

而類是通過type元類來建立的

元類建立類,然後類中有new方法來建立這個類的執行個體

現在我們看看type類内部是怎麼來建立類的。我們可以為 <code>__metaclass__</code> 設定一個type類的派生類,加入print語句,進而檢視類建立的過程。

執行後列印的結果:

執行了 <code>obj = Foo("Bob")</code> 後,從列印的結果可以看出上面的執行順序。

先把 <code>obj = Foo("Bob")</code> 和後面列印對象的2句注釋掉,我們發現雖然沒有調用執行任何語句,隻是定義了2個類,但是<code>MyType.__init__</code> 已經被執行了。因為Foo是元類MyType的一個對象,建立對象是通過類的構造方法,是以要建立Foo這個對象(即Foo類),元類的構造方法就被觸發執行了。而這個Foo是MyType的類的一個對象的關系,就是通過Foo裡的metaclass的值來确定的。

第一個被執行的是<code>MyType.__init__</code>,元類執行它的構造函數,建立了元類的一個執行個體,這裡就是Foo類。然後再是通過 <code>obj = Foo("Bob")</code> 這個執行個體化的語句來觸發了後面的一系列的結果。

第二個被執行的是<code>Mytype.__call__</code>,call方法列印之後,一次會執行後面的3句語句。把這3句全部注釋掉之後,我們會發現不會再有任何輸出。從上到下依次再去掉注釋執行。去掉第一個後發現<code>Foo.__new__</code>被執行了。

第三個被執行的是<code>Foo.__new__</code>,是以類中的new方法是由metaclass指向的那個類(在這裡是MyType)中的call方法來觸發執行的。上面我們已經已經知道new方法需要一個傳回值,而這個傳回值就是傳回給上面的call方法,用來繼續執行下面的語句。現在可以去掉第二個注釋,發現<code>Foo.__init__</code>被執行了。

第四個被執行的是<code>Foo.__init__</code>。這個當然就iFoo的構造方法了。構造方法是在new方法傳回給上面的call方法之後,由call方法使用new的傳回值繼續調用執行的。

最後call方法還有一行<code>return obj</code>,完成了将對象傳回作為傳回值傳回。是以注釋掉之後,列印對象是空,也就是上面一系列的過程執行過之後,生成的是這個obj作為Foo("Bob")這個執行個體話過程的傳回值。

通過字元串映射或修改程式運作時的狀态、屬性、方法, 有以下4個方法

hasattr(obj,name) :判斷對象是否包含對應的屬性

getattr(object, name[, default]) :傳回一個對象屬性值,若沒有對應屬性傳回default,若沒設default将觸發AttributeError

setattr(obj,name,value) :設定對象屬性值。和=指派的等價

delattr(obj,name) :删除對象的屬性,不能删除方法。和del的效果等價

上面說的屬性,對于方法來說都是一樣對待的,還是因為一切皆對象,屬性的了解比較直覺,下面都用方法來舉例子:

<code>setattr(obj,name,value)</code> 這句就相當于是 <code>obj.name = value</code> ,兩句是等價的

<code>delattr(obj,name)</code> 這句就相當于是 <code>del obj.name</code> ,兩句是等價的

就是通過子產品名的字元串形式來導入這個子產品。文法比較簡單,主要是應用場景可能一般用不到,希望有需要的時候還能想到

上面隻能導入子產品,比如 <code>time.asctime</code> 就不是子產品了,導入會報錯。另外還有一個是編譯器内部使用的方法,下面貼出來。不過如果自己用,還是用觀法建議的吧。

在程式設計過程中為了增加友好性,在程式出現bug時一般不會将錯誤資訊顯示給使用者,而是現實一個提示的頁面。

我們可以把可能出現異常的語句放到下面的try裡:

上面可以寫多個except來處理不同的異常類型。如果多個異常類型可以使用相同的出場方法,那麼看下面的例子

再加一個錯誤,讓except同時處理多個異常類型

雖然放到了try裡,但是新的異常種類并沒有寫到except裡,是以依然會抛出錯誤,下面再把這個異常種類寫進去:

try中的代碼塊一旦執行到錯誤,就不會再執行後面的代碼了。捕獲到異常後,直接就去找except。如果錯誤類型不在except裡,仍然會抛出錯誤。如果錯誤類型符合,就執行這個except代碼塊内的代碼,然後跳出整個try代碼塊繼續往後執行。

還可以這樣,把幾種異常種類寫一起

我們還可以使用Exception這個錯誤類型(也可以預設錯誤類型),捕獲所有的錯誤:

雖然什麼錯誤都能捕獲,但是不建議這麼用。建議是,對于特殊處理或提醒的異常需要先定義,最後定義Exception來確定程式正常運作。

而且其實也不是什麼錯誤都能捕獲的。因為try本身也是代碼,如果連編譯器都不能識别的話,就無法執行try來捕獲了,比如

在異常處理最後可以加上else代碼塊,隻有try中的内容無異常順利執行完之後,才會運作esle代碼塊中的内容

無論是否有異常,最後都會執行finally代碼塊中的内容。如果未能捕獲到異常的類型,就會抛出異常然後終止程式運作。是以在抛出異常前會先執行finally裡的代碼塊。這是代碼放在finally中和放到整個異常代碼塊之後的差別,就是報錯前仍然會先把finally裡的執行完再報錯然後終止

基本上所有的情況都有了,那麼異常最複雜的情況大概就是下面這樣,所有的都用上了

使用raise可以主動觸發一個異常,

raise [Exception [, args [, traceback]]] : Exception是異常類型,可以是python有的其他錯誤類型。可以預設但是不能自創,預設的話錯誤類型就是None,後面的一個參數是異常的資訊,也就是上面例子中我們捕獲的e。最後還有一個參數可省略,是跟蹤錯誤對象的,上課沒講也很少用的到。

如果要捕獲這個異常也和上面一樣

首先異常也是類,上面的異常類型,其實都是類名。except比對的異常類型就是比對類名,所有的異常類型都是繼承自Exception,是以可以使用Exception來捕獲所有的異常。另外其實Exception是繼承自BaseException,但是我們平時不需要知道BaseException的存在。

自定義異常我們隻要熟練運用類的方法就可以了。一般就是自定義繼承Exception的新的異常類型,或者自定義繼承自其它異常類型的子類異常類型。

自定義的異常,應該隻是邏輯上有錯誤,影響你程式的正常運作但是不影響python代碼的執行。是以python是不會報錯的,我們要觸發自己定義的異常,都是通過邏輯判斷後主動将自定義的異常通過raise抛出。

自定義異常類中的str方法,是不需要的,因為可以從父類繼承到。這裡寫出來是為了說明,我們列印異常資訊是通過str方法定義的。就是就是把你捕獲到的異常對象通過as指派,然後列印這個對象(列印這個對象就是調用這個對象的str方法)。當然也可以像例子中這樣不繼承,自己重構,自定義異常資訊的處理。

判斷一個條件,為真則繼續,否則抛出異常。異常類型:AssertionError

socket通常也稱作"套接字",用于描述IP位址和端口,是一個通信鍊的句柄,應用程式通常通過"套接字"向網絡送出請求或者應答網絡請求。

建立一個socket必須至少有2端, 一個服務端,一個用戶端, 服務端被動等待并接收請求,用戶端主動發起請求, 連接配接建立之後,雙方可以互發資料。

先要有一個伺服器端server:

運作後,會停留在監聽的地方,直到監聽到服務請求。

然後再寫一個用戶端client:

這裡再運作一下上面的用戶端,同時觀察伺服器端和用戶端的回報資訊。

伺服器端:accept到用戶端的請求後,按我們寫的列印出conn和addr,然後再将接收到的資訊列印出來。最後給用戶端會一條資訊

用戶端:列印出接收到的從伺服器端發來的全部轉成大寫的資訊

上面例子中的結束是以用戶端發送一個空資料觸發的

最後全部關閉連接配接

上面的例子,隻發送了一條資料就斷開了。如果要持續交換資料,那麼需要把交換資料的部分寫到一個循環裡,最好還有一個退出循環出的方法。

服務端:

用戶端:

發不了空,不同協定不同系統發送和接收空的情況都不一樣,有的當做沒有任何操作,而有的會造成阻塞。是以不要嘗試發送空。

例子中的退出的過程:

用戶端,input收到空之後,并沒有将這個空發出去。隻是在輸入空資料後就退出了循環然後close。

服務端,在用戶端斷開後,通過 <code>if not data: break</code>這句觸發跳出了循環。這裡用戶端沒有發送空,而且也發不出空,但是依然觸發了這句。正常recv是讀取緩沖區資料并傳回,如果緩沖區無資料,則阻塞直到緩沖區中有資料,隻有在用戶端close後讀取緩存區才會傳回空,是以這裡能觸發break。如果沒有這句break語句,服務端在用戶端close之後會報錯,異常類型:“ConnectionAbortedError”。是以也可以通過異常處理來退出。

首先,目前我們的服務端一次還是隻能連接配接一個用戶端。并且後這段的後面也不會講到同時處理多個連接配接的情況。

上面的例子在接收到用戶端的連接配接請求後,可以持續為用戶端提供服務。但是當這個用戶端斷開後,服務端也無法繼續提供服務了(即使服務端最後不執行close)。如果希望在一次服務結束後不退出,而是可以繼續準備提供下一次服務,那麼就是要在用戶端斷開後,可以回到監聽的狀态,等待下一個用戶端的連接配接請求。在上面的基礎上,用戶端不用修改,服務端需要再加上一個循環。

用戶端不用改,這裡可以試一下同時連多個用戶端。一個用戶端連接配接成功後,别的用戶端再連接配接也是可以連上的,但是發送不了資料。是能發一次資料,但是這時服務端在為其他用戶端服務,暫時不會回複。等你這個用戶端之前的用戶端都斷開後,服務端會馬上處理你的資料并給你回複。

服務端的話也不需要新的知識。隻是需要用之前學的os子產品或者subprocess子產品,收到資料後作為指令執行然後将結果傳回。

用戶端沒有太大的變動,不過這裡服務端的代碼比較簡單。隻能處理輸入指令後能自動獲得結果并傳回的指令。就先拿個dir或者ls試一下。

上面的代碼比較簡單,不能執行象<code>telnet</code>或者<code>nslookup</code>這類會有互動的指令,也不能是錯誤的指令。因為服務端執行指令後都不會自動回複傳回值發送回用戶端。這樣會造成用戶端和服務端程式阻塞,隻能強行關閉了

這裡因為和作業系統互動了,是以中間會有系統的編碼,最後列印執行結果的時候需要注意一下字元編碼

之前例子中,recv的參數都設定了1024。這裡1024是位元組數限制,一次接收不能超過這個位元組數。可以用上面的例子把這個參數改小一點,然後執行一個傳回資料比較多的指令。比如<code>ipconfig -all</code> 或者 comm = "ping 127.0.0.1 -n 10"。

如果傳入的資料超過了參數的位元組限制,隻會先接收限制的位元組數。不過未接收的部分不會丢棄而是會繼續留在隊列是。等待下一次接收。并且後面傳入的資料也會繼續排隊,要先收完前面的資料才能收到後面的資料。

這個參數不是無限大的,因為即使python可以設定一個很大的值,但是系統層面一次接收不了無限大,是以遇到大檔案的情況的一次是接收不完的,需要反複接收

上面是故意調小了recv的參數,但是實際應用中,傳遞大檔案設定是電影視訊的話可能會超出系統的最大值的,是以一定有一次接收不完的情況,那就需要多收幾次

<code>socket.send</code> 将資料發送到連接配接的套接字。傳回值是要發送的位元組數量,該數量可能小于string的位元組大小。即:可能未将指定内容全部發送

和recv一樣,sent也有位元組數的限制。不過指令本身沒有參數限制,系統還是有限制的。是以要發送大資料,send也需要反複發送多次。不過這裡有一個sendall的方法可以使用

<code>socket.sendll</code> 将資料發送到連接配接的套接字,但在傳回之前會嘗試發送所有資料。成功傳回None,失敗則抛出異常。有了這個方法,發送資料就比較簡單了。其實sendall的内部也是通過遞歸調用send,将所有内容發送出去的。

順便提一句,send有sendall,但是recv隻有這一個方法。

方法上面都提到了,資料太大可能需要多次send或者用sendall,收的時候也要收多次才能收完

發送端,任何檔案都可以以rb的方式打開,然後讀取二進制的内容,再把二進制發送出去。

接收端,同樣以wb方式建立一個檔案,然後把接收到的二進制順序寫入,最後儲存。

如此便能完成檔案的傳送。

開發簡單的FTP:

使用者登入

上傳/下載下傳檔案

不同使用者家目錄不同

檢視目前目錄下檔案

充分使用面向對象知識

本文轉自騎士救兵51CTO部落格,原文連結:http://blog.51cto.com/steed/2048162,如需轉載請自行聯系原作者