Encapsulation
封裝 (Encapsulation)的意思就是把屬性 (attribute) 封在類別中,這還牽涉到程式設計中另一個重要的概念資訊隱藏 (information hiding) ,主要就是不讓外界隨意存取類別的屬性,也就是說,只讓類別的屬性給同個類別的方法 (method) 存取。Python 類別 (class) 的屬性 (attribute) 權限預設是公開的,因此類別以外的地方也可以存取。
範例:
class
Demo:
x = 0
def __init__(self, i):
self.i = i
Demo.x += 1
def __str__(self):
return str(self.i)
def hello(self):
print("hello", self.i)
a =
Demo(9527)
a.hello()
print("hello",
a.i)
print()
print("a.i
=", a.i)
print("Demo.x
=", Demo.x)
Out:
hello 9527
hello
9527
a.i =
9527
Demo.x
= 1
print("hello", a.i)
print("a.i =", a.i)
print("Demo.x =", Demo.x)
上述三個類別以外的地方,直接以句點運算子 . 存取屬性值,然而有時候我們希望做到良好的資訊隱藏 (information hiding) ,也就是說我們不要讓類別定義以外的地方去存取其屬性值,這時,我們可以將屬性設定為私有的,簡單來說,就是在屬性識別字 (identifier) 名稱前加上連續兩個底線符號,ex: __x。
如此一來,若是要在類別以外的地方使用屬性值,需要另外可存取的公開方法 (method) 。
Class method
類別方法 (class method)需要一個特別的參數 (parameter) ,習慣上使用 cls ,這與實體方法的 self 類似,不同的是 cls 用來存取類別的屬性 (attribute)。
例如:
class
Demo:
__x = 0
def __init__(self, i):
self.__i = i
Demo.__x += 1
def __str__(self):
return str(self.__i)
def hello(self):
print("hello", self.__i)
@classmethod
def getX(cls): #利用類別方法 get(x)存取類別屬性__x
return cls.__x
a =
Demo(9527)
a.hello()
print("Demo.__x
=", Demo.getX())
#若修改成print(''Demo.__x = '', Demo.__x) 來直接存取Demo.__x,則會有Error:AttributeError: type object 'Demo' has no attribute '__X'
hello
9527
Demo.__x
= 1
實體方法須先建立一個物件才能呼叫,
Ex:
a = Demo('Tom')
Demo.hello()
類別方法則不用,可以直接呼叫
不需要建立物件a = Demo('Tom')
直接用print(Demo.getX())即可
類別方法預設也都是公開的,若要定義私有的類別方法,也就是只能在類別內呼叫的方法,同樣在方法識別字名稱前加上連續兩個底線符號,這樣的類別方法就變成私有的。
Inheritance
若定義了很多類別 (class) ,這些類別中又具有相當多相同的屬性 (attribute) 或方法 (method) 定義,這時候,可利用 Python 的繼承 (inheritance) 機制,將共通的屬性及方法提取出來,另行定義父類別(superclass) ,然後將原本提出共通屬性及方法改為繼承 (inherit) 父類別的子類別 (subclass) 。
這是從 SubDemo 類別去繼承 Demo ,注意類別名稱後的小括弧中註明父類別。範例:
class
Demo:
__x = 0
def __init__ (self, i):
self.__i = i
Demo.__x += 1
def hello(self):
print("hello", self.__i)
@classmethod
def getX(cls):
return cls.__x
@classmethod
def add(cls):
Demo.__x +=1
class
subDemo(Demo):
pass
#子類別
SubDemo 裡只用了一個 pass 陳述 (statement) ,
#這會使 SubDemo 的內容與父類別 Demo 完全一樣。
a =
Demo("Tom")
a.hello()
b =
subDemo("John")
b.hello()
print("Demo.x
=", Demo.getX())
hello
Tom
hello
John
Demo.x
= 2
isinstance(object, classinfo)
內建函數 (function) isinstance() 可以判斷某一個物件是否為某一個類別所建構的實體 (instance) ,若真則回傳 True ,否則回傳 False。
https://docs.python.org/3/library/functions.html#isinstance
Return true if the object argument is an instance of the classinfo argument, or of a (direct, indirect or virtual) subclass thereof. If object is not an object of the given type, the function always returns false. If classinfo is a tuple of type objects (or recursively, other such tuples), return true if object is an instance of any of the types. If classinfo is not a type or tuple of types and such tuples, a TypeError exception is raised.
issubclass(class, classinfo)
內建函數 issubclass() 則可以判斷某一個類別是否為另一個類別的子類別,同樣的,若真則回
傳 True ,否則回傳 False 。
傳 True ,否則回傳 False 。
範例:
上一個範例最後修改成:
上一個範例最後修改成:
a =
Demo("Tom")
b =
subDemo("John")
#isinstance
print(isinstance(a,
Demo))
print(isinstance(a,
subDemo))
print(isinstance(b,
Demo))
print(isinstance(b,
subDemo))
#issubclass
print(issubclass(subDemo,
Demo))
print(issubclass(Demo,
subDemo))
Out:
#變數 (variable) b 雖然是由 SubDemo 建立的,但是 b 也會是 Demo 的實體,這是由於物件實體的建構過程中,會先建立父類別的部份,因此也會建立屬於 b 的父類別物件實體,使 b 得以運用父類別的屬性及方法。
True
False
True
True
True
False
子類別方法改寫
子類別 (subclass) 可依本身特性設定自己的屬性 (attribute) 與方法 (method) ,也會從父類別 (superclass) 繼承 (inherit) 屬性與方法。一般來說,沒有設定成私有的屬性及方法都會被繼承,子類別可由父類別公開的方法存取父類別私有的屬性。
子類別也可依需要改寫 (override) 父類別的方法,這是說子類別需要用到與父類別具有相同名稱的方法,但是子類別需要的功能有所修改、擴充或增加,因此當子類別裡頭定義與父類別相同名稱的方法時,就會改寫父類別的方法。經過改寫,子類別的方法完全屬於子類別所有。
範例:
Out:
Demo 為父類別,定義四個方法, SubDemo 為子類別,改寫 Demo 的兩個方法,包括 __init__() 與 __str__() 。
class
Demo:
__x = 0
def __init__(self, i):
self.__i = i
Demo.__x += 1
def __str__(self):
return str(self.__i)
def hello(self):
print("hello " +
self.__str__())
@classmethod
def getX(cls):
return cls.__x
class
SubDemo(Demo):
def __init__(self, i, j):
self.__i = i
self.__j = j
def __str__(self):
return str(self.__i) + "+" +
str(self.__j)
a =
SubDemo(12,34)
a.hello()
print("a.__x
=", a.getX())
b =
SubDemo(56, 78)
b.hello()
print("b.__x
=", b.getX())
print()
print("a.__x
=", a.getX())
print("b.__x
=", b.getX())
Out:
hello
12+34
a.__x
= 0
hello
56+78
b.__x
= 0
a.__x
= 0
b.__x
= 0
我們可以發現, Demo 有個 __x 變數,原本用來算有多少 Demo 實體 (instance) 被建立,我們想這應該要包括子類別 SubDemo 的實體數量,也就是說, __x 應該等於 Demo 實體總數加上 SubDemo 實體總數。此例中我們建立兩個 SubDemo 實體,可是 __x 卻等於 0 。
因為此例中 Demo 的 __init__() 方法從頭到尾沒有被呼叫過,因此 __x 始終保持為初值 0 。解決這個問題的方法很簡單,一個幾單的途徑是在子類別 SubDemo 新增一個 __x ,但是這樣一來就只能累計 SubDemo 的實體數量,若是還有其他子類別繼承自 Demo ,這就無法一同列入計算了。
另一個解決途徑是在子類別改寫的方法中先呼叫 (call) 父類別的方法,利用內建函數
super()
class
Demo:
__x = 0
def __init__(self, i):
self.__i = i
Demo.__x += 1
def __str__(self):
return str(self.__i)
def hello(self):
print("hello " +
self.__str__())
@classmethod
def getX(cls):
return cls.__x
class
SubDemo(Demo):
def __init__(self, i, j):
super().__init__(i) #SubDemo 的 __init__() 定義中,我們利用
self.__j = j # super() 呼叫父 類別 Demo 的 __init__() ,
#因此需提供 i 當作參數 (parameter) 。
#注意,這裡 self.__i 變成父類別的私有屬 性。
def __str__(self):
return super().__str__() +
"+" + str(self.__j)
#在 return 後的運算式 (expression) 先呼叫 super().__str__() ,
#因為 self.__i 已經變成父類別 Demo 私有屬性,
#因此需要先呼叫父類別的 __str__() 。
a =
SubDemo(12, 34)
a.hello()
print("a.__x
=", a.getX())
b =
SubDemo(56, 78)
b.hello()
print("b.__x
=", b.getX())
print()
print("a.__x
=", a.getX())
print("b.__x
=", b.getX())
Out:
hello
12+34
a.__x
= 1
hello
56+78
b.__x
= 2
a.__x
= 2
b.__x
= 2
Multiple Inheritance
設計類別 (class) 時,父類別 (superclass) 可以有多個,這是說子類別 (subclass) 能夠多重繼承 (multiple inherit) 多個父類別,使子類別可以有多種特性。這裡須注意一點,當子類別繼承 (inheritance) 超過一個來源的時候,會以寫在最左邊的父類別優先繼承,這是說,多個父類別如果有相同名稱的屬性 (attribute) 與方法 (method) ,例如 __init__() 、 __str__() 等,就會以最左邊的父類別優先。語法如下:
class
SubClass( SuperClass1, SuperClass2):
pass
|
因此,如果 SuperClass1 有 __init__() 、 __str__() 等, SubClass 就會繼承 SuperClass1 的 __init__() 及 __str__() ,而 SuperClass2 的 __init__() 、 __str__() 不會出現在 SubClass 之中。
範例:
#多重繼承
class
Demo:
__x = 0
def __init__(self, i):
self.__i = i
Demo.__x += 1
def hello(self):
print("hello", self.__i)
@classmethod
def getX(cls):
return cls.__x
@classmethod
def add(cls):
Demo.__x +=1
class
Demo2:
def __init__(self, i):
self.__i = i
def reverseString(self,string):
reverse=''
for i in range(len(string)-1, -1, -1):
reverse += string[i]
return reverse
class
subDemo(Demo,Demo2):
def __init__(self, i, j="guest"):
super().__init__(i)
self.__i = i
self.__j = j
def hello(self):
print("hello", self.__i,self.__j)
def superHello(self):
super().__init__(self.__i)
super().hello()
a =
subDemo("Tom")
print(a.reverseString("Tom"))
print("Demo.x
=", Demo.getX())
moT
Demo.x
= 1
__del__()
建構子 (constructor) 用來建立物件 (object) ,當物件不需要被使用時,直譯器 (interpreter) 會主動替物件呼叫 __del__() 方法 (method) ,這是物件自動銷毀的方法,也就是從記憶體中釋放空間的步驟,被稱為解構子 (destructor) ,當然,我們也可以改寫 (override) 這個方法。
範例:
class
Demo:
def __init__(self, i):
self.i = i
def __str__(self):
return 'hello world'
def __del__(self):
print("del called: " +
self.__str__())
def hello(self):
print("hello " +
self.__str__()) #重新定義override
a =
Demo("Tommy")
a.hello()
print(
str(a) )
Out:
hello
hello world
hello
world
del
called: hello world
我們只有使用變數 (variable) a 一個名稱,利用建構子 Demo() 建立物件後呼叫 hello() ,然後重新呼叫 Demo() 建立另一個 Demo 型態的物件,我們可以看到直譯器主動呼叫 del () ,印出 "del called" 的訊息。最後程式結束執行前,直譯器同樣主動呼叫最後建立物件解構子,完全釋放所使用的記憶體空間。
Polymorphism
多型 (polymorphism) 是物件導向程式語言 (object-oriented programming language) 的一項主要特性,使物件 (object) 的使用更具彈性。簡單來說,多型可使物件的型態具有通用的效力,例如以下程式:
Out:
1
0
多型的應用很多,例如串列中可接受不同型態的物件當元素,或是方法可用不同型態的參數等。
沒有留言:
張貼留言