當前位置:
首頁 > 知識 > 淺析 Python 的類、繼承和多態

淺析 Python 的類、繼承和多態

(點擊

上方藍字

,快速關注我們)




來源:adam1q84


segmentfault.com/a/1190000010046025


如有好文章投稿,請點擊 → 這裡了解詳情




類的定義



假如要定義一個類 Point,表示二維的坐標點:





# point.py


class

Point

:


    

def __init__

(

self

,

x

=

0

,

y

=

0

)

:


        

self

.

x

,

self

.

y

=

x

,

y




最最基本的就是 __init__ 方法,相當於 C++ / Java 的構造函數。帶雙下劃線 __ 的方法都是特殊方法,除了 __init__ 還有很多,後面會有介紹。




參數 self 相當於 C++ 的 this,表示當前實例,所有方法都有這個參數,但是調用時並不需要指定。




>>>

from point import *


>>>

p

=

Point

(

10

,

10

)

  

# __init__ 被調用


>>>

type

(

p

)


<

class

"point.Point"

>


>>>

p

.

x

,

p

.

y


(

10

,

10

)




幾乎所有的特殊方法(包括 __init__)都是隱式調用的(不直接調用)。




對一切皆對象的 Python 來說,類自己當然也是對象:





>>>

type

(

Point

)


<

class

"type"

>


>>>

dir

(

Point

)


[

"__class__"

,

"__delattr__"

,

"__dict__"

,

...,

"__init__"

,

...]


>>>

Point

.

__class__


<

class

"type"

>




Point 是 type 的一個實例,這和 p 是 Point 的一個實例是一回事。




現添加方法 set:





class

Point

:


    

...


    

def set

(

self

,

x

,

y

)

:


        

self

.

x

,

self

.

y

=

x

,

y





>>>

p

=

Point

(

10

,

10

)


>>>

p

.

set

(

0

,

0

)


>>>

p

.

x

,

p

.

y


(

0

,

0

)




p.set(...) 其實只是一個語法糖,你也可以寫成 Point.set(p, ...),這樣就能明顯看出 p 就是 self 參數了:





>>>

Point

.

set

(

p

,

0

,

0

)


>>>

p

.

x

,

p

.

y


(

0

,

0

)




值得注意的是,self 並不是關鍵字,甚至可以用其它名字替代,比如 this:





class

Point

:


    

...


    

def set

(

this

,

x

,

y

)

:


        

this

.

x

,

this

.

y

=

x

,

y




與 C++ 不同的是,「成員變數」必須要加 self. 前綴,否則就變成類的屬性(相當於 C++ 靜態成員),而不是對象的屬性了。




訪問控制




Python 沒有 public / protected / private 這樣的訪問控制,如果你非要表示「私有」,習慣是加雙下劃線前綴。





class

Point

:


    

def __init__

(

self

,

x

=

0

,

y

=

0

)

:


        

self

.

__x

,

self

.

__y

=

x

,

y


 


    

def set

(

self

,

x

,

y

)

:


        

self

.

__x

,

self

.

__y

=

x

,

y


 


    

def __f

(

self

)

:


        

pass




__x、__y 和 __f 就相當於私有了:





>>>

p

=

Point

(

10

,

10

)


>>>

p

.

_

_

x


...


AttributeError

:

"Point"

object

has no

attribute

"__x"


>>>

p

.

__f

()


...


AttributeError

:

"Point"

object

has no

attribute

"__f"




_repr_




嘗試列印 Point 實例:





>>>

p

=

Point

(

10

,

10

)


>>>

p


<

point

.

Point

object

at

0x000000000272AA20

>




通常,這並不是我們想要的輸出,我們想要的是:





>>>

p


Point

(

10

,

10

)




添加特殊方法 __repr__ 即可實現:





class

Point

:


    

def __repr__

(

self

)

:


        

return

"Point({}, {})"

.

format

(

self

.

__x

,

self

.

__y

)




不難看出,交互模式在列印 p 時其實是調用了 repr(p):





>>>

repr

(

p

)


"Point(10, 10)"




_str_




如果沒有提供 __str__,str() 預設使用 repr() 的結果。


這兩者都是對象的字元串形式的表示,但還是有點差別的。簡單來說,repr() 的結果面向的是解釋器,通常都是合法的 Python 代碼,比如 Point(10, 10);而 str() 的結果面向用戶,更簡潔,比如 (10, 10)。




按照這個原則,我們為 Point 提供 __str__ 的定義如下:





class

Point

:


    

def __str__

(

self

)

:


        

return

"({}, {})"

.

format

(

self

.

__x

,

self

.

__y

)




_add_




兩個坐標點相加是個很合理的需求。





>>>

p1

=

Point

(

10

,

10

)


>>>

p2

=

Point

(

10

,

10

)


>>>

p3

=

p1

+

p2


Traceback

(

most recent call

last

)

:


  

File

"<stdin>"

,

line

1

,

in

<

module

>


TypeError

:

unsupported operand type

(

s

)

for

+:

"Point"

and

"Point"




添加特殊方法 __add__ 即可做到:





class

Point

:


    

def __add__

(

self

,

other

)

:


        

return

Point

(

self

.

__x

+

other

.

__x

,

self

.

__y

+

other

.

__y

)





>>>

p3

=

p1

+

p2


>>>

p3


Point

(

20

,

20

)




這就像 C++ 里的操作符重載一樣。




Python 的內建類型,比如字元串、列表,都「重載」了 + 操作符。




特殊方法還有很多,這裡就不逐一介紹了。




繼承




舉一個教科書中最常見的例子。Circle 和 Rectangle 繼承自 Shape,不同的圖形,面積(area)計算方式不同。





# shape.py


 


class

Shape

:


    

def area

(

self

)

:


        

return

0.0


        


class

Circle

(

Shape

)

:


    

def __init__

(

self

,

r

=

0.0

)

:


        

self

.

r

=

r


 


    

def area

(

self

)

:


        

return

math

.

pi *

self

.

r *

self

.

r


 


class

Rectangle

(

Shape

)

:


    

def __init__

(

self

,

a

,

b

)

:


        

self

.

a

,

self

.

b

=

a

,

b


 


    

def area

(

self

)

:


        

return

self

.

a *

self

.

b




用法比較直接:





>>>

from shape import *


>>>

circle

=

Circle

(

3.0

)


>>>

circle

.

area

()


28.274333882308138


>>>

rectangle

=

Rectangle

(

2.0

,

3.0

)


>>>

rectangle

.

area

()


6.0




如果 Circle 沒有定義自己的 area:





class

Circle

(

Shape

)

:


    

pass




那麼它將繼承父類 Shape 的 area:





>>>

Shape

.

area

is

Circle

.

area


True




一旦 Circle 定義了自己的 area,從 Shape 繼承而來的那個 area 就被重寫(overwrite)了:





>>>

from shape import *


>>>

Shape

.

area

is

Circle

.

area


False




通過類的字典更能明顯地看清這一點:





>>>

Shape

.

__dict__

[

"area"

]


<

function

Shape

.

area

at

0x0000000001FDB9D8

>


>>>

Circle

.

__dict__

[

"area"

]


<

function

Circle

.

area

at

0x0000000001FDBB70

>




所以,子類重寫父類的方法,其實只是把相同的屬性名綁定到了不同的函數對象。可見 Python 是沒有覆寫(override)的概念的。




同理,即使 Shape 沒有定義 area 也是可以的,Shape 作為「介面」,並不能得到語法的保證。




甚至可以動態的添加方法:





class

Circle

(

Shape

)

:


    

...


    

#  def area(self):


        

#  return math.pi * self.r * self.r


 


# 為 Circle 添加 area 方法。


Circle

.

area

=

lambda

self

:

math

.

pi *

self

.

r *

self

.

r




動態語言一般都是這麼靈活,Python 也不例外。




Python 官方教程「9. Classes」第一句就是:





Compared with other programming languages, Python』s class mechanism adds classes with a minimum of new syntax and semantics.




Python 以最少的新的語法和語義實現了類機制,這一點確實讓人驚嘆,但是也讓 C++ / Java 程序員感到頗為不適。




多態




如前所述,Python 沒有覆寫(override)的概念。嚴格來講,Python 並不支持「多態」。




為了解決繼承結構中介面和實現的問題,或者說為了更好的用 Python 面向介面編程(設計模式所提倡的),我們需要人為的設一些規範。




請考慮 Shape.area() 除了簡單的返回 0.0,有沒有更好的實現?




以內建模塊 asyncio 為例,AbstractEventLoop 原則上是一個介面,類似於 Java 中的介面或 C++ 中的純虛類,但是 Python 並沒有語法去保證這一點,為了盡量體現 AbstractEventLoop 是一個介面,首先在名字上標誌它是抽象的(Abstract),然後讓每個方法都拋出異常 NotImplementedError。





class

AbstractEventLoop

:


    

def run_forever

(

self

)

:


        

raise

NotImplementedError


    

...




縱然如此,你是無法禁止用戶實例化 AbstractEventLoop 的:





loop

=

asyncio

.

AbstractEventLoop

()


try

:


    

loop

.

run_forever

()


except

NotImplementedError

:


    

pass




C++ 可以通過純虛函數或設構造函數為 protected 來避免介面被實例化,Java 就更不用說了,介面就是介面,有完整的語法支持。




你也無法強制子類必須實現「介面」中定義的每一個方法,C++ 的純虛函數可以強制這一點(Java 更不必說)。




就運算元類「自以為」實現了「介面」中的方法,也不能保證方法的名字沒有寫錯,C++ 的 override 關鍵字可以保證這一點(Java 更不必說)。




靜態類型的缺失,讓 Python 很難實現 C++ / Java 那樣嚴格的多態檢查機制。所以面向介面的編程,對 Python 來說,更多的要依靠程序員的素養。




回到 Shape 的例子,仿照 asyncio,我們把「介面」改成這樣:





class

AbstractShape

:


    

def area

(

self

)

:


        

raise

NotImplementedError




這樣,它才更像一個介面。




super




有時候,需要在子類中調用父類的方法。




比如圖形都有顏色這個屬性,所以不妨加一個參數 color 到 __init__:





class

AbstractShape

:


    

def __init__

(

self

,

color

)

:


        

self

.

color

=

color




那麼子類的 __init__() 勢必也要跟著改動:





class

Circle

(

AbstractShape

)

:


    

def __init__

(

self

,

color

,

r

=

0.0

)

:


        

super

().

__init__

(

color

)


        

self

.

r

=

r




通過 super 把 color 傳給父類的 __init__()。其實不用 super 也行:





class

Circle

(

AbstractShape

)

:


    

def __init__

(

self

,

color

,

r

=

0.0

)

:


        

AbstractShape

.

__init__

(

self

,

color

)


        

self

.

r

=

r




但是 super 是推薦的做法,因為它避免了硬編碼,也能處理多繼承的情況。






看完本文有收穫?請轉

發分享給更多人


關注「P

ython開發者」,提升Python技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python 的精彩文章:

用Python做中文分詞就是這麼簡單!
用 Python 進行貝葉斯模型建模(2)
2017年開發者生態報告:Python最多人想嘗試的編程語言
Python版演算法專題 二叉樹的深度優先遍歷
252 行代碼搞定 Python 模板實現

TAG:Python |

您可能感興趣

Python和Scala的類繼承關係分析
金泰熙的美貌繼承人——Cristina Fernandez Lee
Google Home Mini 的繼承者將是 Nest Mini
新iPhone廉價版竟然用上A10處理器,是iPhone SE系列的繼承者嗎
Kotlin 繼承
Transformer 三部曲:RNN 的繼承者
驚!新iPhone廉價版竟然用上A10處理器,是iPhone SE系列的繼承者?
Carven以「Madame Carven的虛構女繼承人」為主題
蘋果iPhone XS繼承了iPhoneX的哪些東西
Cohiba Medio Siglo——高希霸世紀系列的繼承者,依舊是美味的代名詞
榮耀MagicBook intel版上手:繼承手機的高性價比,成筆記本新寵
iPhone XS 模範評測:iPhone X 的完美繼承者,「全面屏」時代最好的入場券
iPhone 9首次曝光:在設計中依舊繼承iPhone X多個基因
iPhone 9機模曝光 設計上全部繼承iPhone X基因
Swift 繼承
PSP時代的輝煌,新款Switch能繼承嗎?
金在中有望出演新劇《Jane the virgin》 飾演酒店繼承人
Gucci力捧?Andy Warhol的繼承人?這幫鬼才藝術家七月集結上海
比Faker還慘,uzi繼承人遭隊友排擠,ming和走A照片:確認過眼神
繼承Find X的口碑,R17 Pro受到外媒好評不斷