淺析 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 進行貝葉斯模型建模(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受到外媒好評不斷