Python之面向對象與類

本節內容


  1. 面向對象的概念
  2. 類的封裝
  3. 類的繼承
  4. 類的多態
  5. 靜態方法、類方法 和 屬性方法
  6. 類的特殊成員方法
  7. 繼承層級關係中子類的實例對象對屬性的查找順序問題

1、面向對象的概念


1. "面向對象(OOP)"是什麼?

簡單點說,「面向對象」是一種編程範式,而編程範式是按照不一樣的編程特色總結出來的編程方式。俗話說,條條大路通羅馬,也就說咱們使用不一樣的方法均可以達到最終的目的,可是有些辦法比較快速、安全且效果好,有些方法則效率低下且效果不盡人意。一樣,編程也是爲了解決問題,而解決問題能夠有多種不一樣的視角和思路,前人把其中一些廣泛適用且行之有效的編程模式歸結爲「範式」。常見的編程範式有:css

  • 面向過程編程:OPP(Procedure Oriented Programing)
  • 面向對象編程:OOP(Object Oriented Programing)
  • 函數式編程:(Functional Programing)

面向過程編程的步驟:

1)分析出解決問題所須要的步驟;html

2)用函數把這些步驟一次實現;java

3)一個一個地調用這些函數來解決問題;python

面向對象編程的步驟:

1)把構成問題的事務分解、抽象成各個對象;nginx

2)結合這些對象的共有屬性,抽象出類;sql

3)類層次化結構設計--繼承 和 合成;shell

4)用類和實例進行設計和實現來解決問題。編程

關於面向對象編程 與 面向過程編程的區別與優缺點能夠參考這篇文章swift

2. 面向對象編程的特色

面向對象編程達到了軟件工程的3個目標:重用性、靈活性、擴展性,而這些目標是經過如下幾個主要特色實現的:安全

  • 封裝: 能夠隱藏實現細節,使代碼模塊化
  • 繼承: 能夠經過擴展已存在的類來實現代碼重用,避免重複編寫相同的代碼
  • 多態: 封裝和繼承的目的都是爲了實現 代碼重用, 而多態是爲了實現 接口重用,使得類在繼承和派生的時候可以保證任何一個類的實例都能正確調用約定好的屬性和方法。簡單來講,就是爲了約定相同的屬性和方法名稱。

須要說明的是,Python不像Java中又專門的「接口」定義,Python中的接口與類沒有什麼區別,可是咱們能夠經過在一個用於當作接口的類中所定義的方法體中 raise NotImplementedError異常,來強制子類必須從新實現該方法。

3. 面向對象編程的使用場景

咱們知道,Python既能夠面向過程編程,也能夠面向對象編程。那麼什麼場景下應該使用面向對象編程呢?若是咱們僅僅是寫一個簡單的腳原本跑一些簡單的任務,咱們直接用面向過程編程就行了,簡單,快速。當咱們須要實現一個複雜的系統時,或者如下場景下,就須要使用面向對象編程:

  • 場景1: 當多個函數須要傳入多個共同的參數時,能夠將這些函數封裝到一個類中,並將這些參數提取爲這個類的屬性;
  • 場景2: 當須要根據一個模板來建立某些東西時,能夠經過類來完成。

2、類的封裝


封裝是面向對象的主要特徵之一,是對象和類概念的主要特性。簡單的說,一個類就是一個封裝了數據以及操做這些數據的方法的邏輯實體,它向外暴露部分數據和方法,屏蔽具體的實現細節。除此以外,在一個對象內部,某些數據或方法能夠是私有的,這些私有的數據或方法是不容許外界訪問的。經過這種方式,對象對內部數據提供了不一樣級別的保護以防止程序中無關的部分意外的改變或錯誤使用了對象的私有部分,好比java中修飾類變量和方法的相關關鍵字有:private、protected, public等。下面咱們經過類的定義和實例化的實例來講明一下Python中的是如何實現對這些不一樣等級數據的保護的。

1. 類的定義

類的定義是對顯示事務的抽象過程和能力,類是一個對象/實例的模板,也是一個特殊的對象/實例(由於Pythobn中一切皆對象,因此類自己也是一個對象)

如今咱們來定義個Person類,它有如下3個屬性:

  • nationality:國籍
  • name:姓名
  • id:身份證號碼

假設如今咱們有如下幾個前提:

  • 全部人的國籍基本都是相同的,且容許直接經過類或實例來訪問,容許隨意修改
  • 大部分人的姓名是不一樣的,且容許直接經過類的實例來訪問和隨意修改
  • 全部人的身份證號碼都是不同的,且不容許直接經過類或實例來訪問或隨意修改
import uuid class Person(object): nationality = 'China' def __init__(self, name): self.name = name self.__id = str(uuid.uuid1()) def hello(self): print('Hi, i am %s, from %s, my id is %s' % (self.name, self.nationality, self.__id)) def get_and_print_id(self): print(self.__id) return self.__id 

2. 類的實例化

類實例化的方式:類名([參數...]),參數是__init__方法中除了第一個self參數以外的其餘參數,上面定義的這個Person類中,實例化時須要傳遞的參數只有一個name。好比咱們來實例化3個Person對象,他們的name分別是 tom 和 jerry:

tom = Person('tom') jerry = Person('jerry') jack = Person('jack')

3. 不一樣保護等級的屬性說明

公有屬性/類屬性

直接定義在class下的屬性就是公有屬性/類屬性,好比上面那個Person類中的nationality屬性。「公有」的意思是這個屬性是這個類的全部實例對象共同全部的,所以默認狀況下這個屬性值值保留一份,而不會爲該類的每一個實例都保存一份。

print(Person.nationality, tom.nationality, jerry.nationality, jack.nationality)

tom.nationality = 'USA' print(Person.nationality, tom.nationality, jerry.nationality, jack.nationality) Person.nationality = 'India' print(Person.nationality, tom.nationality, jerry.nationality, jack.nationality)

輸出結果以下:

China China China China China USA China China India USA India India

結論:

- 公有屬性/靜態屬性 能夠直接經過類直接訪問,也能夠直接經過實例進行訪問; - 經過類的某個實例對公有屬性進行修改,實際上對爲該實例添加了一個與類的公有屬性名稱相同的成員屬性,對真正的公有屬性是沒有影響的,所以它不會影響其餘實例獲取的該公有屬性的值; - 經過類對公有屬性進行修改,必然是會改變公有屬性原有的值,他對該類全部的實例是都有影響的。

成員屬性/實例屬性

成員屬性,又稱成員變量 或 實例屬性,也就是說這些屬性是 該類的每一個實例對象單獨持有的屬性。成員屬性須要在類的__init__方法中進行聲明,好比上面的Person類中定義的name屬性就是一個成員屬性。

print(tom.name, jerry.name, jack.name) jerry.name = 'jerry01' print(tom.name, jerry.name, jack.name)

輸出結果:

tom jerry jack tom jerry01 jack

來看看能不能直接經過類訪問成員屬性

print(Person.name)

輸出結果:

Traceback (most recent call last): ... AttributeError: type object 'Person' has no attribute 'name'

結論:

- 成員屬性能夠直接經過實例對象來訪問和更改; - 成員屬性是每一個實例對象獨有的,某個實例對象的成員屬性被更改不會影響其餘實例對象的相同屬性的值; - 成員屬性的值不能經過類來訪問和修改;

私有屬性

私有屬性和成員屬性同樣,是在__init__方法中進行聲明,可是屬性名須要以雙下劃線__開頭,好比上面定義的Person中的__id屬性。私有屬性是一種特殊的成員屬性,它只容許在實例對象的內部(成員方法或私有方法中)訪問,而不容許在實例對象的外部經過實例對象或類來直接訪問,也不能被子類繼承。

經過實例對象訪問私有屬性:

print(tom.__id)

輸出結果

Traceback (most recent call last): ... AttributeError: 'Person' object has no attribute '__id'

經過類訪問私有屬性:

print(Person.__id)

輸出結果:

Traceback (most recent call last): ... AttributeError: type object 'Person' has no attribute '__id'

經過類的成員方法訪問私有屬性:

tom.hello() jerry.hello() jack.hello()

輸出結果:

Hi, i am tom, from China, my id is b6ac08c6-9dae-11e7-993f-208984d7aa83 Hi, i am jerry, from China, my id is b6ac08c7-9dae-11e7-b508-208984d7aa83 Hi, i am jack, from China, my id is b6ac08c8-9dae-11e7-9ace-208984d7aa83

結論:

- 私有變量不能經過類直接訪問; - 私有變量也不能經過實例對象直接訪問; - 私有變量能夠經過成員方法進行訪問。

那麼要訪問私有變量怎麼辦呢? 有兩種辦法:

辦法1:經過一個專門的成員方法返回該私有變量的值,好比上面定義的get_id()方法,搞過java的同窗很天然就會想到java類中的set和get方法。

tom_id = tom.get_id()
jerry_id = jerry.get_id()
jack_id = jack.get_id()

print(tom_id, jerry_id, jack_id)

輸出結果:

46bc6b5c-9dd6-11e7-8306-208984d7aa83 46cbfe68-9dd6-11e7-b5d1-208984d7aa83 46cbfe69-9dd6-11e7-9b5c-208984d7aa83

辦法2:經過 實例對象._類名__私有變量名 的方式來訪問

print(tom._Person__id, jerry._Person__id, jack._Person__id)

輸出結果:

e1f4ee86-9dd6-11e7-a186-208984d7aa83 e1f5b1f8-9dd6-11e7-b1c3-208984d7aa83 e1f5b1f9-9dd6-11e7-b74a-208984d7aa83

總結

  • 公有屬性、成員屬性 和 私有屬性 的受保護等級是依次遞增的;
  • 私有屬性 和 成員屬性 是存放在已實例化的對象中的,每一個對象都會保存一份;
  • 公有屬性是保存在類中的,只保存一份;
  • 哪些屬性應該是公有屬性的,哪些屬性應該是私有屬性 須要根據具體業務需求來肯定。

3、類的繼承


1. 繼承的相關概念

繼成 和 組合 是類的兩個最主要的關係,而 繼承 關係的類之間是有層級的。被繼承的類被稱爲 父類、基類 或 超類 ;繼承的類被稱爲 子類 或 派生類

2. 繼承的做用

繼承 是一個從通常到特殊的過程, 子類能夠繼承現有類的全部功能,而不須要從新實現代碼。簡單來講就是 繼承提升了代碼重用性和擴展性

3. 繼承的分類

Python中類的繼承按照父類中的方法是否已實現可分爲兩種:

  • 實現繼承 :指直接繼承父類的屬性和已定義並實現的的方法;
  • 接口繼承 :僅繼承父類類的屬性和方法名稱,子類必須自行實現方法的具體功能代碼。

若是是根據要繼承的父類的個數來分,有能夠分爲:

  • 單繼承: 只繼承1個父類
  • 多繼承: 繼承多個父類
    一般,咱們都是用 單繼承 ,不多用到 多繼承。

4. 類繼承實例

類的繼承關係

父類/基類/超類--Person
class Person(object): def __init__(self, name, age): self.name = name self.age = age def walk(self): print('%s is walking...' % self.name) def talk(self): print('%s is talking...' % self.name )
子類--Teacher
class Teacher(Person): def __init__(self, name, age, level, salary): super(Teacher, self).__init__(name, age) self.level = level self.salary = salary def teach(self): print('%s is teaching...' % self.name)
子類--Class
class Student(Person): def __init__(self, name, age, class_): Person.__init__(self, name, age) self.class_ = class_ def study(self): print('%s is studying...' % self.name)

子類實例化

t1 = Teacher('張老師', 33, '高級教師', 20000) s1 = Student('小明', 13, '初一3班') t1.talk() t1.walk() t1.teach() s1.talk() s1.walk() s1.study()

輸出結果:

張老師 is talking... 張老師 is walking... 張老師 is teaching... 小明 is talking... 小明 is walking... 小明 is studying...

繼承說明

  • Teacher類 和 Student類 都繼承 Person類,所以Teacher和Student是Person的子類/派生類,而Person是Teacher和Student的父類/基類/超類;
  • Teacher和Student對Person的繼承屬於實現繼承,且是單繼承;
  • Teacher類繼承了Person的name和age屬性,及talk()和walk()方法,並擴展了本身的level和salary屬性,及teach()方法;
  • Student類繼承了Person的name和age屬性,及talk()和walk()方法,並擴展了本身的class屬性,及study()方法;
  • Teacher和Student對Person類屬性和方法繼承體現了 「代碼的重用性」, 而Teacher和Student擴展的屬性和方法體現了 「靈活的擴展性」;
  • 子類須要在本身的__init__方法中的第一行位置調用父類的構造方法,上面給出了兩種方法:
  • super(子類名, self).__init__(父類構造參數),如super.(Teacher, self).__init__(name, age)
  • 父類名.__init__(self, 父類構造參數),如Person.__init__(self, name, age),這是老式的用法。
  • 子類 Teacher 和 Student 也能夠在本身的類定義中 從新定義 父類中的talk()和walk()方法,改變其實現代碼,這叫作方法重寫

關於多繼承,以及多繼承時屬性查找順序(廣度優先、深度優先)的問題會在下面進行單獨說明。

4、類的多態


多態是指,相同的成員方法名稱,可是成員方法的行爲(代碼實現)卻各不相同。這裏所說的多態是經過 繼承接口的方式實現的。Java中有interface,可是Python中沒有。Python中能夠經過在一個成員方法體中拋出一個NotImplementedError異常來強制繼承該接口的子類在調用該方法前必須先實現該方法的功能代碼。

接口--Animal

class Animal(object): def __init__(self, name): self.name = name def walk(self): raise NotImplemented('Subclass must implement the abstract method by self') def talk(self): raise NotImplemented('Subclass must implement the abstract method by self') 

子類--Dog

class Dog(Animal): pass

執行代碼:

dog = Dog('大黃') dog.talk()

輸出結果:

Traceback (most recent call last): ... raise NotImplemented('Subclass must implement the abstract method by self') TypeError: 'NotImplementedType' object is not callable

可見,此時子類必須本身先實現talk方法才能調用。

實現了接口方法的子類--Dog 和 Duck

class Dog(Animal): def talk(self): print('%s is talking:旺旺...' % self.name) def walk(self): print('%s 是一條小狗,用4條腿走路' % self.name) class Duck(Animal): def talk(self): print('%s is talking: 嘎嘎...' % self.name) def walk(self): print('%s 是一隻鴨子,用兩條腿走路' % self.name)

執行代碼:

dog = Dog('大黃') dog.talk() dog.walk() duck = Duck('小白') duck.talk() duck.walk()

輸出結果:

大黃 is talking:旺旺... 大黃 是一條小狗,用4條腿走路 小白 is talking: 嘎嘎... 小白 是一隻鴨子,用兩條腿走路

由此可知:

  • 接口的全部子類擁有接口中定義的全部同名的方法;
  • 接口的全部子類在調用接口中定義的方法時,必須先本身實現方法代碼;
  • 接口的各個子類在實現接口中同一個方法時,具體的代碼實現各不相同,這就是多態。

5、屬性方法、類方法、靜態方法


上面提到過,類中封裝的是數據和操做數據的方法。數據就是屬性,且上面已經介紹過了屬性分爲:
公有屬性/類變量、成員屬性/實例變量 和 私有屬性。如今咱們來講說類中的方法,類中的方法分爲如下幾種:

  • 成員方法: 上面定義的都是成員方法,一般狀況下,它們與成員屬性類似,是經過類的實例對象去訪問;成員方法的第一個參數必須是當前實例對象,一般寫爲self;實際上,咱們也能夠經過類名來調用成員方法,只是此時咱們須要手動的傳遞一個該類的實例對象給成員方法的self參數,這樣用明顯不是一種優雅的方法,所以基本不會這樣使用。

  • 私有方法: 以雙下劃線開頭的成員方法就是私有方法,與私有屬性相似,只能在實例對象內部訪問,且不能被子類繼承;私有方法的第一個參數也必須是當前實例對象自己,一般寫爲self;

  • 類方法: 以@classmethod來裝飾的成員方法就叫作類方法,它要求第一次參數必須是當前類。與公有屬性/靜態屬性 類似,除了可經過實例對象進行訪問,還能夠直接經過類名去訪問,且第一個參數表示的是當前類,一般寫爲cls;另外須要說明的是,類方法只能訪問公有屬性,不能訪問成員屬性,所以第一個參數傳遞的是表明當前類的cls,而不是表示實例對象的self。

  • 靜態方法: 以@staticmethod來裝飾的成員方法就叫作靜態方法,靜態方法一般都是經過類名去訪問,且嚴格意義上來說,靜態方法已經與這個類沒有任何關聯了,由於靜態方法不要求必須傳遞實例對象或類參數,這種狀況下它不能訪問類中的任何屬性和方法。

  • 屬性方法: 這個比較有意思,是指能夠像訪問成員屬性那樣去訪問這個方法;它的第一個參數也必須是當前實例對象,且該方法必需要有返回值。

咱們先來定義這樣一個類:

import uuid class Person(object): nationality = 'China' def __init__(self, name, age): self.name = name self.age = age self.__id = str(uuid.uuid1()) # 成員方法/實例方法 def sayHello(self): print('Hello, i am %s from %s, i am %d years old.' % (self.name, self.nationality, self.age)) # 私有方法 def __func0(self): print('private method: func0') print(self.name, self.age, self.__id, self.nationality) # 類方法  @classmethod def func1(cls): print(cls.nationality) # 靜態方法  @staticmethod def func2(a, b): print(a + b) # 屬性方法  @property def func3(self): return '%s: %d' % (self.name, self.age)

執行代碼:

p = Person('Tom', 18) p.sayHello() Person.sayHello(p) Person.func1() p.func1() Person.func2(3, 4) p.func2(3, 4) print(p.func3)

輸出結果:

Hello, i am Tom from China, i am 18 years old. Hello, i am Tom from China, i am 18 years old. China China 7 7 Tom: 18

總結:

  • 成員方法也能夠經過類名去訪問,可是有點畫蛇添足的感受;
  • 類方法和靜態方法也能夠經過實例對象去訪問,可是一般狀況下都是經過類名直接訪問的;
  • 最重要的一條總結:類的各類方法,能訪問哪些屬性其實是跟方法的參數有關的:
  • 好比成員方法要求第一個參數必須是一個該類的實例對象,那麼實例對象能訪問的屬性,成員方法都能訪問,並且還能訪問私有屬性;
  • 再好比,類方法要求第一個參數必須是當前類,所以它只能訪問到類屬性/公有屬性,而訪問不到成員屬性 和 私有屬性;
  • 再好比,靜態方法對參數沒有要求,也就意味着咱們能夠任意給靜態方法定義參數;假如咱們給靜態方法定義了表示當前類的參數,那麼就能夠訪問類屬性/公有屬性;假如咱們給靜態方法定義了表示當前類的實例對象的參數,那麼就能夠訪問成員屬性;假如咱們沒有給靜態方法定義這兩個參數,那麼就不能訪問該類或實例對象的任何屬性。

6、類的特殊成員屬性及特殊成員方法


咱們上面提到過:名稱以雙下劃線__開頭的屬性是私有屬性,名稱以雙下劃線__開頭的方法是私有方法。這裏咱們要來講明的是,Python的類中有一些內置的、特殊的屬性和方法,它們的名稱是以雙下劃線__開頭,同時又以雙下劃線__結尾。這些屬性和方法再也不是私有屬性和私有方法,它們是能夠在類的外部經過實例對象去直接訪問的,且它們都有着各自特殊的意義,咱們能夠經過這些特殊屬性和特殊方法來獲取一些重要的信息,或執行一些有用的操做。

1. 類的特殊成員屬性

屬性名稱 說明
__doc__ 類的描述信息
__module__ 表示當前操做的對象對應的類的定義所在的模塊名
__class__ 表示當前操做的對象對應的類名
__dict__ 一個字典,保存類的全部的成員(包括屬性和方法)或實例對象中的全部成員屬性

如今來看一個實例:

在dog.py模塊定義一個Dog類

class Dog(object): """這是一個Dog類""" # print('Hello, This is a dog.') color = '白色' def __init__(self, name): self.name = name self.__id = 1234 def func1(self): pass def __func1(self): pass  @classmethod def func2(cls): pass  @staticmethod def func3(): pass 

在test.py模塊執行下面的代碼

from dog import Dog

dog1 = Dog('泰迪')
print(dog1.__doc__) print(dog1.__module__) print(dog1.__class__) print(dog1.__dict__) print(Dog.__dict__)

輸出結果

這是一個Dog類
dog
<class 'dog.Dog'> {'name': '泰迪', '_Dog__id': 1234} {'__dict__': <attribute '__dict__' of 'Dog' objects>, '__module__': 'dog', 'func2': <classmethod object at 0x000001DF0C658F98>, 'color': '白色', 'func3': <staticmethod object at 0x000001DF0C658FD0>, '_Dog__func1': <function Dog.__func1 at 0x000001DF0C63E400>, '__weakref__': <attribute '__weakref__' of 'Dog' objects>, '__doc__': '這是一個Dog類', '__init__': <function Dog.__init__ at 0x000001DF0C63E2F0>, 'func1': <function Dog.func1 at 0x000001DF0C63E378>}

總結:

  • 實例對象.__dict__ 和 類.__dict__ 的值是不一樣的:實例對象.__dict__的值中只包含成員屬性和私有屬性,類.__dict__的值中包含公有屬性/類屬性和全部類型的方法;
  • __module__和__class__的值可用於反射來實例化一個類的對象,下面會介紹。

2. 類的特殊成員方法

方法名稱 說明
__init__ 類構造方法,經過類建立對象時會自動觸發執行該方法
__del__ 析構方法,當對象在內存中被什邡市,會自動觸發執行該方法。好比實例對象的做用域退出時,或者執行 del 實例對象操做時。
__str__ 若是一個類中定義了__str__方法,那麼在打印對象時默認輸出該方法的返回值,不然會打印出該實例對象的內存地址。
__xxxitem__ 是指__getitem__、__setitem__、__delitem這3個方法,它們用於索引操做,好比對字典的操做,分別表示 獲取、設置、刪除某個條目。 數據。能夠經過這些方法來定義一個類對字典進行封裝,從而能夠對字典中key的操做進行控制,尤爲是刪除操做。
__new__ 該方法會在__init__方法以前被執行,該方法會建立被返回一個新的實例對象,而後傳遞給__init__。另外須要說明的是,這不是一個成員方法,而是一個靜態方法。
__call__ 源碼中的註釋是"Call self as a function." 意思是把本身(實例對象)做爲一個函數去調用,而函數的調用方式是函數名()。也就是說,當咱們執行實例對象()或者 類名()()這樣的操做時會觸發執行該方法。

示例1

先來定義這樣一個類:

class Person(object): def __call__(self, *args, **kwargs): print(self.name, '__call__') def __init__(self, name, age): self.name = name self.age = age print(self.name, '__init__') def __del__(self): print(self.name, '__del__') def __str__(self): print(self.name, '__str__') return '%s: %d'% (self.name, self.age)

執行下面的代碼:

print('--------實例化對象-----------') p = Person('Tom', 18) print('--------打印實例對象-----------') print(p) print('--------把實例對象做爲方法進行調用-----------') p() # 等價於 Person('Tom', 18)() print('--------程序運行結束-----------')

輸出結果:

--------實例化對象-----------
Tom __init__ --------打印實例對象----------- Tom __str__ Tom: 18 --------把實例對象做爲方法進行調用----------- Tom __call__ --------程序運行結束----------- Tom __del__

能夠看到,全部代碼都執行完後,進程退出時實例對象的__del__方法才被調用,這是由於對象要被銷燬了。

示例2

定義一個相似字典的類

class MyDict(object): def __init__(self, init=None): self.__dict = init if init is not None else {} def __setitem__(self, key, value): print('__setitem__', key) self.__dict[key] = value def __getitem__(self, item): print('__getitem__', item) return self.__dict.get(item, None) def __delitem__(self, key): print('__delitem__', key) if key is not None and key.startswith('wh'): print('You can not delete this item ') return None return self.__dict.pop(key, None)

執行下面的代碼

# 類實例化與get item
my_dict = MyDict(init={'what': '打豆豆', 'who': '企鵝團', 'time': '吃飽睡好以後'}) print(my_dict['who'], my_dict['time'], my_dict['what']) # set item my_dict['num'] = '10次' print(my_dict['who'], my_dict['time'], my_dict['what'], my_dict['num']) # del item del my_dict['num'] print(my_dict['num']) del my_dict['what'] print(my_dict['what'])

輸出結果

__getitem__ who __getitem__ time __getitem__ what 企鵝團 吃飽睡好以後 打豆豆 __setitem__ num __getitem__ who __getitem__ time __getitem__ what __getitem__ num 企鵝團 吃飽睡好以後 打豆豆 10次 __delitem__ num __getitem__ num None __delitem__ what You can not delete this item __getitem__ what 打豆豆

可見,若是一個類實現了__setitem__,__getitem、__delitem 這幾個方法,就能夠執行一些相似字典同樣的操做,好比上面用到的:

  • my_dict['KEY'] 會自動調用my_dict實例對象的__getitem__方法;
  • my_dict['KEY'] = VALUE 會自動調用my_dict實例對象的__setitem__方法;
  • del my_dict['KEY'] 會自動調用my_dict實例獨享的__delitem__方法;
    而咱們定義這樣一個類的目的在於,咱們能夠更好對字典操做進行控制,好比上面的例子中咱們不容許刪除key以'wh'開頭的條目。

7、繼承層級關係中子類的實例對象對屬性的查找順序問題


有的同窗說:「這個還不簡單嗎?子類確定是先找本身有沒有這個屬性或方法,有的話直接調用本身的,沒有再去父類裏面找。」 沒毛病,確實是這樣的,可是咱們真的理解這句話了嗎?另外,若是是多繼承且有多個類的層級關係,查找順序又是怎樣的呢?再來簡單描述下這裏要討論的問題是什麼,簡單來講,就是要你們搞明白2個問題:

  • 1)子類的實例對象調用的某個屬性或方法究竟是父類的仍是本身的;
  • 2)若是是多繼承(同時繼承多個父類),調用的的究竟是哪一個父類的屬性或方法,查找順序是怎樣的。

若是你們把這2問題搞明白了,這裏目的就達到了。下面咱們分別以單繼承和多繼承兩個實例來說解咱們這裏要說明的問題。

1. 單繼承的狀況

A、B、C三個類的定義以下:

class A(object): def __init__(self, name): self.name = name def func1(self): print('class A: func1') def func2(self): print('class A: func2') class B(A): def __init__(self, name, age): super(B, self).__init__(name) self.age = age def func2(self): print('class B: func2') class C(A): def func1(self): print('class C: func1') def func3(self): print('class C: func3') 

如今要執行這段代碼:

objB = B('Tom', 18) objC = C('Jerry') print(objB.name, objB.age) print(objC.name) objB.func1() objC.func1()

請先思考下面代碼的輸出結果,再去看下面的答案。

輸出結果:

Tom 18 Jerry class A: func1 class C: func1

這是最簡單的狀況,也是最容易用開頭那段話來解釋清楚的狀況,所以不作過多贅述。但這只是個引子,真正要討論的是下面這種狀況:

A與B兩個類的定義以下:

class A(object): def __init__(self, name): self.name = name def func1(self): print('class A: func1') self.func2() def func2(self): print('class A: func2') class B(A): def __init__(self, name, age): super(B, self).__init__(name) self.age = age def func2(self): print('class B: func2')

如今要執行下面的代碼:

objB = B('Tom', 18) objB.func1()

你內心有答案了嗎?func1必然是執行的父類 A中的func1,由於子類B中沒有這個方法。那麼父類func1中調用的func2方法,究竟是父類的仍是子類的呢?解決了這個疑問,答案天然就出來了。

分析1:

class B 是class A的子類,所以它會繼承class A的的方法func1和func2。可是,class B已經重寫了func2,能夠理解爲class A中的func2方法已經被覆蓋了,class B如今只能看到本身重寫後的那個func2方法,因此func1中調用的應該是class B 重寫後的func2方法。

分析2:

有的同窗可能不太能理解,class A中的方法怎麼能調用class B中的方法呢?下面咱們來看下class B與class
A的包含關係圖:

由於子類 class B繼承了 class A的內容,所以綠框中的內容(class A)是屬於藍框(class B)中的一部分,他們應該看作一個總體。綠框中的func1是能夠調用綠框外的func2的,由於他們都是objB中的成員方法。其實理解這些以後,如今咱們來套用開始那句話:

  • func1方法的查找與調用: objB調用func1方法,發現class B自己(藍框內的直接內容)並無該方法,因此去父類class A(藍框內的綠框)中去找,發現找到了,因而進行調用;
  • func2方法的查找與調用: func1中調用了func2方法,這個時候仍是先找子類class B自己(藍框內的直接內容),發現找到了,因而直接調用子類本身的func2方法。

所以上面這段代碼的執行結果是:

class A: func1 class B: func2

2. 多繼承的狀況

新式類 與 經典類

Python 2.2引入了新式類,與它對應的是經典類,這裏咱們僅僅是解釋下他們的概念,爲講解下面的內容作鋪墊,不會深刻討論的它們的之間的區別。這裏咱們主要說明一下幾個點就能夠了:

  • Python 2.x中,默認是經典類,只有顯示繼承了object的纔是新式類;
  • Python 3.x中,默認就是新式類,經典類已經被廢棄;
  • 新式類的子類也是新式類

深度優先 與 廣度優先

深度優先 能夠理解爲 縱向優先,廣度優先 能夠理解爲 水平方法優先。咱們知道,類與類之間是有層級關係的,父類與子類是縱向的層級關係,同一個父類的多個直接子類是水平方向的同級關係。

上圖中 A是父類、B和C是繼承A的子類,D是同時繼承B和C的子類。此時D的一個實例對象去查找一個父類中的屬性或方法的查找順序就有兩種可能,可是這兩種查找順序中第一個查找的父類必然都是B:

  • B-->A-->C:這就是深度優先,由於優先查找的是與B上一層級的、縱向的A
  • B-->C-->A:這就是廣度優先,由於優先查找的是與B同一層極的、水平方向的C

實例

定義如下幾個類:

class A(object): def func1(self): print('class A: func1') def func2(self): print('class A: func2') class B(A): def func3(self): print('class B: func3') class C(A): def func1(self): print('class C: func1') class D(B, C): pass

執行以下代碼:

objD = D()
objD.func1()

Python 2.7 和 Python 3.5的輸出結果都是同樣的:

class C: func1

咱們更改下A的定義,不顯示的指定其繼承object:

class A(): def func1(self): print('class A: func1') def func2(self): print('class A: func2') class B(A): def func3(self): print('class B: func3') class C(A): def func1(self): print('class C: func1') class D(B, C): pass

再來執行一樣的代碼:

objD = D()
objD.func1()

Python 2.7的輸出結果:

class A: func1

Python 3.5的輸出結果:

class C: func1

結論

前面咱們已經說過了 在Python 3.x中不管是否顯示指定繼承object,全部的類都是新式類,那麼咱們根據上面的兩個實例的輸出結果能夠得出這樣的結論:在多繼承的狀況下,經典類查找父類屬性或方法的順序是深度優先,新式類查找父類屬性的順序是廣度優先。

相關文章
相關標籤/搜索