Python學習入門教程(28)—類(之三)

(本號正在連續推出以Python官網文檔為主線的系統學習Python的系列文章或視頻,感興趣的朋友們歡迎搜索關注。在這裡學習Python事半功倍!本文及後續文章如無特別聲明均以Windows平臺作為演示平臺,Python版本為:3.8.1)


【本篇繼上篇繼續講解關於"類"的內容】

相關的一些討論

如果相同的屬性名同時出現在實例和它的類中,那麼屬性查找將優先考慮該實例:

<code>>>> class Warehouse:
purpose = 'storage'
region = 'west'

>>> w1 = Warehouse()
>>> print(w1.purpose, w1.region)
storage west
>>> w2 = Warehouse()
>>> w2.region = 'east'
>>> print(w2.purpose, w2.region)
storage east/<code>

數據屬性既可以由方法引用,也可以由對象的普通用戶引用。換句話說,Python的類不能用於實現純抽象數據類型。實際上,Python沒有保證數據隱藏的任何措施,所謂的數據隱藏都是基於慣例的,也就是說大家約定把某種命名形式的數據作為私有數據對待,但任何用戶仍舊可以只直接引用它。(需要說明的是,用C語言編寫的Python實現可以完全隱藏實現細節,並在必要時控制對對象的訪問,這可以用於用C編寫的Python擴展)

用戶應該謹慎地使用數據屬性,因為稍有不慎用戶可能會因為隨意修改數據屬性而破壞了本來該由方法維護的數據的一致性。注意,只要不發生名稱衝突,用戶可以在不影響方法有效性的情況下將自己的數據屬性添加到實例對象中,但是遵守命名約定才能省去很多不必要的麻煩。

從方法中引用數據屬性(或其他方法)沒有簡便方式。這樣實際上可以增加方法的可讀性,當閱讀一個方法時,不可能混淆局部變量和實例變量。

通常,方法的第一個參數名為self。這只不過是一個約定,名稱self對Python絕對沒有特殊的含義。但是注意,如果不遵循約定,您的代碼對其他Python程序員的可讀性可能會降低,而且有的類瀏覽器程序可能是依賴於這種約定編寫的。

類的任何函數對象屬性都為該類的實例定義了一個方法。函數定義不需要封裝在類定義中,完全可以將類外定義的函數對象賦值給類中的局部變量。例如:

<code># Function defined outside the class
def f1(self, x, y):
return min(x, x+y)

class C:
f = f1

def g(self):
return 'hello world'

h = g/<code>

現在f、g和h都是C類的屬性,用來引用函數對象,因此它們都是類C的實例的方法。h與g是完全等價的。注意,這種類外定義函數的做法不是一種好做法,通常只會使程序的閱讀者感到困惑。

實例方法可以通過使用self參數的方法屬性來調用其他方法:

<code>class Bag:
def __init__(self):
self.data = []

def add(self, x):
self.data.append(x)

def addtwice(self, x):
self.add(x)
self.add(x)/<code>

方法可以以與普通函數相同的方式引用全局名稱。與方法關聯的全局作用域是包含其定義的模塊。類永遠不用作全局作用域。儘管很少有在方法中使用全局數據的必要,但是全局作用域有許多合理的用法。例如,方法可以使用導入到全局作用域的函數和模塊,以及其中定義的函數和類。通常,包含該方法的類本身是在這個全局作用域中定義的。

在Python中每個值都是一個對象,因此也就有一個類(也稱為其類型)與其對應,並且被存儲在object.__ class__中。

繼承

Python支持類的繼承,派生類定義的語法如下:

<code>class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-n>/<statement-1>/<code>

名稱BaseClassName必須定義在包含派生類定義的範圍中。基類名還允許使用其他表達形式。這可能是很有用的,例如,當基類在另一個模塊中定義時:

<code>class DerivedClassName(modname.BaseClassName):
<statement-1>
.
.
.
<statement-n>/<statement-1>/<code>

派生類定義的執行過程與基類相同。當類對象被構造時,基類會被記住用於解析屬性引用,如果在類中沒有找到請求的屬性,則會繼續在基類中查找。如果基類本身派生自其他類,則遞歸應用此規則。

派生類的實例化沒有什麼特別之處:DerivedClassName()創建一個類的新實例。方法引用的解析如下:首先搜索相應的類屬性,如果在當前類中沒有找到該屬性,則將沿基類鏈繼續搜索。如果在此過程中找到了函數對象,則方法引用就是有效的;如果沒找到,則會報錯。

派生類可以重寫其基類的方法。由於方法在調用其對象的其他方法時沒有特別的優先權,所以基類的方法如果調用同一基類中定義的另一個方法時,則可能最終調用覆蓋該類的派生類的方法。(對於C++程序員來說,Python中的所有方法實際上都是虛函數。)

派生類中的重寫了的方法實際上可能希望擴展而不是簡單地替換同名的基類方法。有一種直接調用基類方法的簡單方法:調用BaseClassName.methodname(self,arguments)。這有時對用戶也是有用的。(注意,只有在基類作為BaseClassName在全局作用域內可訪問時才有效。)

Python有兩個與繼承相關的內置函數:

  • isinstance() 用於檢查一個實例的類型。例如,isinstance(obj, int)只有在obj為int型或者是派生自int型的類時才為真。
  • issubclass() 用於檢查類繼承關係。例如issubclass(bool, int)為真,因為bool是int的子類,而issubclass(float, int)為假,因為float不是int的子類。

多繼承

Python還支持多重繼承,也就是可以定義包含多個基類的子類。定義是這樣的:

<code>class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-n>/<statement-1>/<code>

在大多數最簡單的情況下,可以將對繼承自父類的屬性的搜索視為從左到右的深度優先搜索,而不是在繼承結構中有重疊的同一個類中搜索兩次。如果在DerivedClassName中沒有找到屬性,則在Base1中搜索它,然後(遞歸地)在Base1的基類中搜索它,如果在基類中沒有找到,則在Base2中搜索它,依此類推。

事實上,實際的搜索過程要稍微複雜一些,為了支持對super()的調用,解析順序是動態變化的。這種方法在其他一些多繼承語言中稱為call-next-method,它比單繼承語言中的super調用更強大。

多繼承中動態排序是必要的,因為多繼承關係中很有可能出現一個或多個菱形關係(其中至少有一個父類可以從最下面的類通過多條路徑訪問)。例如,所有類都繼承自object,因此任何情況下的多重繼承都提供了到達object的多個路徑。為了防止不止一次地訪問此類基類,動態算法按照每個類中指定的父類以從左到右的順序線性化了搜索順序,從而保證每個父類只被單調地調用一次。這樣一來,一個類可以派生子類而不影響對其父類搜索時的優先順序。總之,這些屬性使設計具有多重繼承的可靠的可擴展類成為可能。


[關於"類"部分的內容本篇未完,下篇將繼續講解]

【結束】

篇尾寄語:萬丈高樓平地起,是否具有紮實的基礎決定一個人能否走遠以及能走多遠。Python的學習也是同樣的道理!


分享到:


相關文章: