2016年5月31日 星期二

Python_Note08

物件導向程式設計 (object-oriented programming) 有三大基本特性,分別是封裝 (encapsulation)繼承 (inheritance) 多型 (polymorphism)

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'

Out:
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())

Out:
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 。
Return true if class is a subclass (direct, indirect or virtual) of classinfo. A class is considered a subclass of itself. classinfo may be a tuple of class objects, in which case every entry in classinfo will be checked. In any other case, a TypeError exception is raised.

範例:
上一個範例最後修改成:
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:
True
False
True
True
True

False
#變數 (variable) b 雖然是由 SubDemo 建立的,但是 b 也會是 Demo 的實體,這是由於物件實體的建構過程中,會先建立父類別的部份,因此也會建立屬於 b 的父類別物件實體,使 b 得以運用父類別的屬性及方法。

子類別方法改寫

子類別 (subclass) 可依本身特性設定自己的屬性 (attribute) 與方法 (method) ,也會從父類別 (superclass) 繼承 (inherit) 屬性與方法。一般來說,沒有設定成私有的屬性及方法都會被繼承,子類別可由父類別公開的方法存取父類別私有的屬性。

子類別也可依需要改寫 (override) 父類別的方法,這是說子類別需要用到與父類別具有相同名稱的方法,但是子類別需要的功能有所修改、擴充或增加,因此當子類別裡頭定義與父類別相同名稱的方法時,就會改寫父類別的方法。經過改寫,子類別的方法完全屬於子類別所有。

範例:
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 為父類別,定義四個方法, SubDemo 為子類別,改寫 Demo 的兩個方法,包括 __init__() 與 __str__() 。
我們可以發現, 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())

Out:
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:
Missy: Meow!
Garfield: Meow!
Lassie: Woof! Woof!

其他範例:
d1 = '1,2,3,4,5'    #d1只能輸入str
d2 = [1,2,3,4,'5']      #d2可以輸入list, float等,取決於是甚麼型態去使用coun

print(d1.count('4'))
print(d2.count('4'))

Out:
1
0

d1 為字串 (string) , d2 為串列 (list) ,兩者皆屬於序列 (sequence) 的複合資料型態 (compound data type),有通用的 count() 方法,可計算某元素 (element) 累計出現的次數。

多型的應用很多,例如串列中可接受不同型態的物件當元素,或是方法可用不同型態的參數等。



沒有留言:

張貼留言