Python面向對象編程-封裝

大家使用Python這門語言,大多是作爲一門腳本語言來使用。這裏熟悉下python的面向對象的特性。

1、幾個基本概念

1.1 面向過程和麪向對象

根據操作數據的函數或語句塊來設計程序的過程,叫做面向過程編程。與之相對應的,如果把數據和功能結合起來,用稱爲對象的東西包裹起來,這種組織程序的方法稱爲面向對象的編程。面向對象的編程一般用在稍微大型的項目或者是要求更加合理的解決方案的場合,相對與面向過程的編程有優勢。

1.2 面向對象的特性

面向對象的編程有三個特性:封裝、繼承和多態,這裏會分別作介紹。

1.3 類和對象

類就是一個新的、程序員自己根據需求定義的一個新的類型,而對象是這個類型的一個實例。比如說,定義了一個新的類型Person,然後用這個類型定義了一個變量tom,這裏,tom就是一個對象,而Person就是一個類。

2、封裝

面向對象的程序設計中,某個類把所需要的數據(也可以說是類的屬性)和對數據的操作(也可以說是類的行爲)全部都封裝在類中,分別稱爲類的成員變量和方法(或成員函數)。這種把成員變量和成員函數封裝在一起的編程特性稱爲封裝。

2.1 類的屬性

類由屬性和方法組成,類的屬性是對數據的封裝,而類的方法是對類的行爲的封裝。類的屬性按使用範圍分爲共有屬性和私有屬性。具體地,在python實現面向對象的編程思想的時候,封裝在類中的屬性可以分爲兩種數據類的屬性和數據對象的屬性(也可以成爲數據類的成員變量和屬於對象的成員變量),其中,這兩種成員變量又各自分爲共有成員變量和私有成員變量。

2.1.1 類的成員變量和對象的成員變量

類的成員變量定義在類中,和類的成員函數在同一縮進等級。而對象的成員變量定義在__init__(self)成員函數中,和__init__(self)函數下的變量和語句在同一等級。通俗地講,類的成員變量屬於類,共類的所有對象和類本身所共有,也就是說所有的類的對象和類只有一份這樣的變量。而對象的成員變量屬於類的對象本身,每個對象都有一份,而且各個對象之間互不影響。

2.1.2 公有成員變量和私有成員變量

python中用成員變量的名字來區分是共有成員變量或者是私有成員變量。python中,以兩個下劃線‘__’開頭的變量都是私有成員變量,而其餘的變量都屬於公有成員變量。其中,私有的成員變量只能在類的內部訪問,而共有的公有的成員變量可以在類的外部進行訪問。

總結起來有如下的四種成員變量其使用方法如下圖1。


圖1 類屬性的訪問權限

要注意的是,這裏有一個例外但是緊緊用來體調試程序。可以通過“objName._ClsName__attriName”的方式來訪問類的私有變量,但是注意這種方式也僅僅用於調試程序(比如可以用tom._Person__gold來訪問Person類的私有成員變量__gold)。

除了上面提到的屬性之外,類還有一種屬性叫內置屬性,它們是在系統定義類的時候默認添加的,由前後兩個下劃線命名。

  • __dict__ : 類的屬性(包含一個字典,由類的數據屬性組成)
  • __doc__ :類的文檔字符串
  • __name__: 類名
  • __module__: 類定義所在的模塊(類的全名是'__main__.className',如果類位於一個導入模塊mymod中,那麼className.__module__ 等於 mymod)
  • __bases__ : 類的所有父類構成元素(包含了以個由所有父類組成的元組)

2.2 類的方法

2.2.1 公有方法和私有方法

如上面所說,類的方法是對類行爲的封裝。類的方法也分爲公有方法和私有方法。類的私有方法只能通過對象名(在類內部也就是self)在類的內部進行訪問,而公有方法可以在類的外部通過對象名進行訪問。和屬性不同的是,一般意義上的類方法屬於對象,也就是說只有通過對象纔可以進行調用,不能直接通過類名進行調用。一般類方法的第一個參數必須是代指類對象本身的(一般我們常用self,實際上可以是任何自定義的名字,只不過self是大家約定俗成的用法,在下面介紹的類方法中,大家一般用cls,因爲那裏更多地標識的是一個類),可以通過self訪問類對象的成員函數和數據。

同樣,公有的成員函數和私有的成員函數也是通過名字來區分的,雙下劃線‘__’開頭的函數是私有成員函數。

2.2.2 類方法和靜態方法

python中對象有兩種:經典對象和新型對象。經典對象就是不繼承object父類定義出來的對象,新型對象就是要繼承object類定義出的對象。新型對象和經典對象最大的區別就是,新型對象中提供了對類方法和靜態方法的支持。

類方法就是被classmethod()函數處理過的函數,能夠直接通過類名進行調用,也能夠被對象直接調用。靜態方法相當於類層面的全局函數,可以被類直接調用,可以被所有實例化對象共享,通過staticmethod()定義靜態方法,靜態方法沒有self參數。當然,也可以用更加方便的裝飾器@classmethod和@staticmethod來定義類方法和靜態方法。

下面的代碼說明了類方法和靜態方法的區別:

class A ( object ):   
    def foo ( self , x ):   
        print "executing foo(%s,%s)" %( self , x )   
  
    @classmethod   
    def class_foo ( cls , x ):   
        print "executing class_foo(%s,%s)" %( cls , x )   
  
    @staticmethod   
    def static_foo ( x ):   
        print "executing static_foo(%s)" % x  
下面是對象實例調用方法的通常做法。對象實例A,隱式傳遞給方法的第一個參數。(譯者加:也就是將實例a對象傳遞給foo方法的self參數)。
a.foo(1)   
# executing foo(<__main__.A object at 0xb7dbef0c>,1)
類方法的方式,類的對象實例作爲隱式的參數傳遞給第一個參數而不是self。(譯者加:也就是將類A傳遞給對象實例的class_foo方法的cls參數)
a.class_foo(1)   
# executing class_foo(<class '__main__.A'>,1)
你甚至可以通過類調用class_foo方法。其實,如果你定義類方法內容,可能你就是想通過類調用它而不是通過對象實例。A.foo(1)會引發類型錯誤異常(TypeError),A.class_foo(1)不會。   
而靜態方法,既不需要self(對象實例)也不需要cls(類)作爲隱藏參數被傳遞。
a.static_foo(1)
# executing static_foo(1)   
foo只是個函數,但是當你調用a.foo時你不是簡單的調用函數,實際上你調用了一個加工過的函數版本:綁定對象實例a作爲第一個參數的函數。foo需要2個參數,而a.foo只需要一個函數(譯者加:對象實例a自動傳遞給了第一個參數self嘛)
a綁定到了foo。這就是爲什麼下面的終端輸出"bound"的原因。
print (a.foo)   
# <bound method A.foo of <__main__.A object at 0xb7d52f0c>>   
而a.class_foo,對象實例a是沒有綁定到foo的,而是類A綁定到foo。
print (a.class_foo)   
# <bound method type.class_foo of <class '__main__.A'>>
這裏,靜態方法,雖然是一個方法,但是a.static_foo只是一個沒有綁定任何參數的完好的函數。static_foo需要1個參數,同樣a.static_foo也只需要一個參數。
print (a.static_foo)   
# <function static_foo at 0xb7d479cc>
[1]以上內容來自下面的參考鏈接

2.2.2 構造函數和析構函數

2.2.2.1 __init__和__del__

python中的構造函數是__init__(),用於初始化類對象的初始化,__init__()方法是可選的,如果不提供,python會給出一個默認的__init__()方法。python中的析構函數是__del__(),用於釋放對象佔用的資源。python中析構函數也是可選的,如果不提供,則python會提供默認的析構函數。如果要顯示調用析構函數,可以使用del關鍵字。

關於__init__()構造函數要注意點,上面說到對象的變量定義在__init__()函數中。其實,除了__init__()函數,其他函數通過傳入的self參數,也可以給對象增加一個變量,在其它對象中可以訪問到。這裏,上面之所以這樣說,是因爲__init__()函數的好處是不用自己顯示調用,在對象創建的時候,自動被調用。這樣 ,在__init__()中定義的變量就會有一個效果就是在對象存在的時候,它們就已經存在了,這實際上更符合我們對於對象的變量的期望,因此,我們就說對象的變量一般定義在__init__函數中。

2.2.2.2 python的垃圾回收機制

Python採用垃圾回收機制來清理不再使用的對象,python中提供了gc模塊來釋放不再使用的對象。python採用“引用計數”的方式來處理回收,也就是說當某個對象在其作用域內再也不被其它對象引用的時候,Python就自動清除對象。Python中也提供了collect()函數,來一次性收集所有待處理的對象,並返回待處理對象的數目(gc.collect())。

3、例子

3.1 該例子中涵蓋了python成員函數、類變量,對象變量的各種訪問和調用方式。

#!/usr/bin/python
#-*-coding:utf8-*-
class Person():
    #人口數和土地面積屬於人類共享的信息,設置爲類公有變量
    population = 0
    earth = 10000
    #諾亞方舟能再多少人,自然不應該讓每個人都知道,設置爲類私有變量
    __noahCapacity =10

    def __init__(self, name, age, salary):
        #名字就是用來讓大家知道的,每個人不一樣,所以這裏設置爲對象的公有變量
        self.name = name
        #年齡也類似,設置爲對象的公有變量
        self.age = age
        #薪水當然屬於每個人都很敏感的啦,設置爲對象的私有變量
        self.__salary = salary

    def __privateFun(self):
        print "這是私有成員函數的輸出"

    #在類的內部訪問對象的公有和私有變量
    def printInstance(self):
        print "*"*50,"公有成員函數被調用","*"*50
        print "在類內部可以通過self訪問對象的公有和私有變量:"
        print "name: %s age: %d salary:%d" % (self.name, self.age, self.__salary)
        print "通過類內部的成員函數,在類的內部調用私有成員函數:"
        self.__privateFun()
        print "*"*120

    #在類的內部訪問類的公有變量和私有變量
    def printClass(self):
        print "-"*50,"公有成員函數被調用","-"*50
        print "在類內部可以通過self訪問類的公有和私有變量:"
        print "population: %d earth: %d NoahCapacity: %d" % (self.population, self.earth, self.__noahCapacity)
        print "在類內部也可以通過類名訪問類的公有和私有變量:"
        print "population: %d earth: %d NoahCapacity: %d" % (Person.population, Person.earth, Person.__noahCapacity)
        print "-"*120

if __name__ == "__main__":
    tom = Person("Tom", 24, 9000)

    print "在類外部訪問屬性操作","|"*100
    print "在類外部可以通過對象名訪問類的公有變量:"
    print "population: %d earth: %d" % (tom.population, tom.earth)

    print "在類外部也可以通過對象名用特殊方法訪問類的私有變量(僅用於調試):"
    print "NoahCapacity: %d" % tom._Person__noahCapacity

    print "在類外部可以通過對象名訪問對象的公有變量:"
    print "name: %s age: %d" % (tom.name, tom.age)

    print "在類外部也可以通過類名訪問類的公有變量:"
    print "population: %d earth: %d" % (Person.population, Person.earth)
    print "|"*120

    print ""

    print "在類的外部調用類的成員函數","|"*100
    print "在類外通過對象名調用類的公有成員函數"
    tom.printInstance()
    tom.printClass()
    print "|"*120
運行效果稍顯亂,不過還好,如下圖2。


圖2 類的成員的訪問和調用

3.2 靜態方法和類方法

#!/usr/bin/python
#-*-coding:utf8-*-
class A (object):   
    attri = "類公有屬性1"
    __attri = "類私有屬性1"
    def foo(self, x):   
        print "executing foo(%s,%s)" % (self, x)   

    @classmethod   
    def class_foo(cls, x):   
        print cls.attri,cls.__attri
        print "executing class_foo(%s,%s)" % (cls, x)   

    @staticmethod 
    def static_foo(x):   
        print "executing static_foo(%s)" % x

    #如下的方法,用classmethod方法也可以將一個方法處理成類方法
    #def fun1(cls, x):   
    #    print "executing class_foo(%s,%s)" % (cls, x)   
    #class_foo = classmethod(fun1)

    #如下的方法,用classmethod方法也可以將一個方法處理成類方法
    #def fun2(x):   
    #   print "executing static_foo(%s)" % x
    #static_foo = staticmethod(fun2)

if __name__ == "__main__":
    a = A()
    a.foo(1)
    a.static_foo(12)
    A.static_foo(12)
    a.class_foo(33)
    A.class_foo(33)
運行結果如下圖3。


圖3 靜態方法和類方法

從上面的結果可以看出,實際上,類方法是綁定到類的,類方法的第一個參數代表的實際是一個類,可以通過它來訪問類的成員變量。靜態方法沒有self參數,靜態方法只不過是一個放在類內部的一個函數罷了,除了放的位置和調用的時候要加一個類名或者對象名限定之外,貌似和一個類外部的函數沒有多少區別。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章