<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,如需轉載請自行聯系原作者