23 - 面向對象基礎-封裝-屬性-方法-訪問控制

1 面向對象介紹

        面向對象是一種程序設計思想,它把對象做爲程序的基本單元,一個對象包含了數據和操做數據的函數。但並非全部語言都支持面向對象編程的。簡單的從語言自己來分的話,主要分爲如下三種:(並非說其餘類型的語言很差,只是場景不適合而已,就比如操做系統,基本都是使用C語言編寫的,語言沒有優劣,只有適不適合)數據庫

  1. 面向機器(彙編語言等):抽象成機器指令,機器容易理解。
  2. 面向過程(C語言等):作一件事,排除個步驟,第一步幹什麼,第二部幹什麼,若是出現狀況A,作什麼處理,若是出現過程B,作什麼處理。問題規模小,能夠步驟話,循序漸進的處理。
  3. 面向對象OOP(Java語言,C++語言,Python等):針對狀況複雜的狀況,好比表示一個大樓的做用,使用函數就比較麻煩了。編程

    如今大部分語言都是面向對象的,它適合軟件規模比較大的場景使用數據結構

2 面向對象

        那什麼是面向對象?咱們能夠簡單來講它就是一套方法論,是 一種認識世界、分析世界的方法論 。將萬事萬物都抽象爲各類對象,在Python中一切皆對象。下面來講一下,主要的名詞含義:app

2.1 類class

抽象的概念,是一類事物的共同特徵的集合。函數

  • 數據:有沒有耳朵,有沒有鼻子
  • 操做事物的能力:能不能吃,能不能說等

用計算機語言來描述,就是屬性(數據)和方法(操做事物的能力)的集合。但凡說分類:都是抽象的概念學習

2.2 對象instance/object

        對象是類的具象、是一個實體。對於咱們每一個個體,都是抽象概念人類的不一樣實體(對象,實例),好比你吃魚,你是對象,魚也是對象,吃是動做.
PS:在python中咱們說類的實例一般會用instance或者object來指代。測試

  • 屬性:對象狀態的抽象,用數據結構來描述
  • 操做:對象行爲的抽象,用操做名和實現該操做的方法來描述(函數)操作系統

    每一個人都是人類的一個單獨實例,都有本身的名字、身高、體重等信息,這些信息是我的的屬性,可是這些信息不能保存在人類中,由於它是抽象的概念,不能保存具體的信息,而咱們每一個人,每一個個體,都是具體的人,就能夠存儲這些具體的屬性,並且不一樣的人具備不一樣的屬性。設計

2.3 Python的哲學思想

在Python中一切皆對象,對象是數據和操做的封裝,對象是獨立的,可是對象之間能夠相互做用(你推我,我打你,相互做用)。

目前OOP是最接近人類認知的編程範式

3 面向對象的要素

面向對象有三大特色:

  1. 封裝(Encapsulation),將數據和操做組裝到一塊兒,對外只暴露一些藉口,經過接口訪問對象。(好比駕駛員駕駛汽車,不須要了解汽車的構造,只須要知道使用什麼部件怎麼駕駛皆能夠了,踩了油門就能跑,能夠不瞭解其中的機動原理。)
  2. 繼承(Inheritance),多複用(繼承來的就不用寫了),遵循多繼承少修改原則,OCP(Open-closed Principle),使用繼承來改變,來體現個性(修改實例本身,不要去修改父類)
  3. 多態(Polymorphism),面向對象編程最靈活的地方,動態綁定,好比人吃和貓吃,都是吃,可是不一樣吃又不太同樣。簡單來講就是:同一種方法,在不一樣的子類上,表現方式不一樣。(父類、子類經過繼承聯繫在一塊兒,經過一套方法,就能夠實現不一樣的表現,就是多態)

4 Python的類

對象是特徵(變量)與技能(函數)的結合,類是一些列對象共有的特徵與技能的結合體.

  • 在現實生活中:先有對象,再總結概括出的類
  • 在程序中:必定先定義類,再實例化出對象

定義一個類:

class ClassName:
    語句塊

規範:

  1. 必須使用class關鍵字
  2. 類名必須是用大駝峯命名
  3. 類定義完成後(被解釋器一行一行解釋完畢後),就產生一個類對象,綁定到了標識符ClassName上(一切皆對象思想,類對象也是某個對象(type)的實例)
class MyClass:              # class 類名稱
    '''MyClass Doc'''       # 類介紹,經過類對象的__doc__屬性獲取
    x = 100                 # 類屬性
    def showme(self):       # 類方法名(函數)
        print('My Class')   # 函數體
    
    showme = lambda self: print('My Class')     # 等同於上面定義的函數
p = MyClass()
print(p.__doc__)

4.1 類對象及屬性

  • 類對象:類的定義執行後會產生一個類對象
  • 類的屬性:類定義中的變量和類中定義的方法都是類的屬性
  • 類變量:x是類MyClass的變量

因此根據上面例子來講:

  1. MyClass中,x,showme都是類的屬性,__doc__也是類的特殊屬性。
  2. showme被叫作method(方法),本質上就是普通的函數對象function,它通常要求至少有一個參數。第一個形式參數能夠是self(self只是一個慣用標識符,能夠換名字,可是不建議,指代當前實例自己)。當使用某個實例調用方法時,這個實例對象會被注入給第一個參數self。因此,self指代的就是實例自己。

4.2 實例化

經過使用類名加括號來構建實例化一個對象。

a = Person() # 實例化
  1. 類名後面加括號,就調用類的實例化方法,完成實例化。
  2. 實例化就真正建立一個該類的對象(實例),每一次實例化都會返回不一樣的實例對象
daxin = Person()
xiaoming = Person()

注意:上面的兩次實例化產生的都是不一樣的實例,即使是參數相同。Python類實例化後,會自動調用__init__方法。這個方法第一個形式參數必須留給self,其餘參數隨意。

4.2.1 __init__函數

        在Python中對象的實例化,其實分爲兩個部分,即實例化和初始化。主要對應__new__和__init__方法。

  1. 實例化(new):獲得一個真正的具體的實例
  2. 初始化(init):對這個真正的實例進行定製化(不能返回,或return None)

__變量名__ 的方法叫作魔術方法

        __new__方法比較複雜,通常用在元類的定義和修改上,這裏先記住:__new__方法實例化一個對象的過程當中會調用__init__方法進行初始化,初始化完畢後再由__new__方法將產生的實例對象進行返回,當__new__方法和__init__方法沒有被定義時,會隱式的向上(父類)查找並調用。

class Person():
    def __init__(self, name, age):  # 形參
        self.name = name   # 這裏daxin 賦給 self.name
        self.age = age   # 這裏20 賦給 self.age
    def sing(self):
        print('{} is sing'.format(self.name))
daxin = Person('daxin',20)    # 實參

注意:

  1. __init__: 的self是由__new__方法注入進來的,不用手動傳遞,這個self就是實例化後的對象。
  2. self.name和self.age:表示對實例化添加了兩個屬性name和age,並進行初始化賦值。
  3. __init__: 只是添加了一些定製化的屬性,並不會返回對象。
  4. daxin 是由__new__方法返回的具體的實例化對象。

注意:__init__方法只有在實例化的時候纔會被調用。

4.2.2 實例對象(instance)

        類實例化後必定會得到一個類的實例,就是實例對象。__init__方法的第一個變量self就是實例自己。經過實例化咱們能夠得到一個實例對象,好比上面的daxin,咱們能夠經過daxin.sing()來調用sing方法,可是咱們並無傳遞self參數啊,這是由於類實例化後,獲得一個實例對象,實例對象會綁定方法上,在使用daxin.sing()進行調用時,會把方法的調用者daxin實例,做爲第一個參數self傳入。而self.name就是daxin實例的name屬性,name保存在daxin實例中,而不是Person類中,因此這裏的name被叫作實例變量。

類中定義的方法會存放在類中(僅存一份),而不是實例中,實例直接綁定到方法上。

class Person:
    def __init__(self,name):
        self.name = name

    def sing(self):
        print('{} is sing'.format(self.name))

daxin = Person('daxin')
print(daxin.sing)   # <bound method Person.sing of <__main__.Person object at 0x00000198EADD83C8>>  綁定方法
print(Person.sing)  # <function Person.sing at 0x00000198EADD99D8>  方法函數

        上例中,在調用daxin.sing時,daxin實例是被綁定到了sing方法上,當 a = Person('a') 時,a.sing其實也是把a綁定到了sing方法上,仔細看的話,不一樣實例的sing方法的內存地址是相同的.

4.2.3 實例變量和類變量

        實例變量是每個實例本身的變量,是本身獨有的。類變量是類的變量,是類的全部實例共享的屬性和方法。下面咱們從一個例子來看實例變量和類變量

In [2]: class Person: 
   ...:     country = 'China'         # 類變量
   ...:     def __init__(self, name, gender): 
   ...:         self.name = name      # 實例變量
   ...:         self.gender = gender 
   ...:                                                                                                 

In [3]: daxin = Person('daxin','male')                                                                  

In [4]: daxin.country                                                                          
Out[4]: 'China'

In [5]: daxin.name                                                                                      
Out[5]: 'daxin'

In [6]: Person.country                                                                                  
Out[6]: 'China'

In [7]: Person.name                                                                                     
---------------------------------------------------------------------------
AttributeError

觀察例子,咱們能夠發現:

  1. 類變量咱們能夠經過類訪問,也能夠經過實例訪問
  2. 實例變量只能經過實例訪問(由於類自己是不會知道誰是他的實力)

    類變量能夠經過實例調用,但實例變量是實例私有的,沒法經過類調用。

PS: 獲取類屬性的不一樣的方式

daxin.age
daxin.__class__.age
Person.age
type(daxin).age

有幾個特殊的屬性先來看一下,便於後續理解

特殊屬性 含義
__name__ 獲取類對象的對象名(返回字符串)
__class__ 獲取實例對象的類型(至關於type),type和__class__返回的是一樣的東西
__dict__ 對象的屬性的字典
__qualname__ 類的限定名
In [8]: daxin.__class__                                                                                 
Out[8]: __main__.Person

In [9]: Person.__class__                                                                                
Out[9]: type

In [10]: type(daxin)                                                                                    
Out[10]: __main__.Person

In [11]: type(daxin) is Person                                                                          
Out[11]: True

In [13]: Person.__name__                                                                                 
Out[13]: 'Person'   # 返回字符串

疑問:當類變量和實例變量重名時,到底訪問的是哪一個?

class Person:
    age = 3
    height = 170

    def __init__(self, name, age=18):
        self.name = name
        self.age = age


tom = Person('Tom')
jerry = Person('jetty', 20)

Person.age = 30
print(1, Person.age, tom.age, jerry.age) 
print(2, Person.height, tom.height, jerry.height)
jerry.height = 175
print(3, Person.height, tom.height, jerry.height)
tom.height += 10
print(4, Person.height, tom.height, jerry.height)
Person.height += 15
print(5, Person.height, tom.height, jerry.height)
Person.weight = 70
print(6, Person.weight, tom.weight, jerry.weight)

分析:

  1. Person返回的是本身的類屬性,因此Person.age是30,tom優先訪問本身的實例變量,因爲指定了默認值,因此tom.age是18,jerry指定了age,因此jerry.age是20.
  2. Person返回的是本身的類屬性,因此Person.height是170,tom和jerry沒有height實例變量,因此訪問的都是類變量,值得都是170
  3. Person.height是170,jerry.height=175 賦值即定義了一個實例變量height,因此tom.height = 170, jerry.height = 175
  4. Person.height是170, tom.height += 10 賦值即定義了一個實例變量height, 因此tom.height = tom.height + 10 = 180, jerry.height = 175
  5. Person.height += 15,Person.height = 185,tom和jerry有本身的實例變量不會收Person影響,因此tom.height = 170, jerry.height=175
  6. Person.weight = 70,爲Person賦值即定義了一個新的weight屬性,因此Person.weight = 70,tom和jerry沒有weight屬性,訪問Person的因此值都是70

經過觀察發現,當類變量和實例變量重名時,彷佛有必定的查找規則,那麼就要說說__dict__了。

4.2.4 __dict__和變量查找順序

        __dict__是一個很是重要的特殊屬性,它是一個存放着對象的屬性信息的字典(對實例來講是字典,對類來講是映射)。

In [14]: Person.__dict__                                                                                
Out[14]: 
mappingproxy({'__module__': '__main__',
              'country': 'China',
              '__init__': <function __main__.Person.__init__(self, name, gender)>,
              '__dict__': <attribute '__dict__' of 'Person' objects>,
              '__weakref__': <attribute '__weakref__' of 'Person' objects>,
              '__doc__': None})

In [15]: daxin.__dict__                                                                                 
Out[15]: {'name': 'daxin', 'gender': 'male'}

In [19]: daxin.__dict__['name']         # 由於__dict__是一個字典,咱們能夠直接經過key來訪問                                                              
Out[19]: 'daxin'

經過觀察發現:

  1. Person.__dict__:包含全部類內定義的屬性及方法。
  2. daxin.__dict__ : 只記錄實例本身的屬性信息。

方法是記錄在類的__dict__中的

實例屬性查找順序: 指的是實例使用.點號來訪問屬性,會先找本身的__dict__,若是沒有,而後經過屬性__class__找到本身的類,而後再在類的__dict__中查找.直接使用實例.__dict__[變量名],不會在類中查找。

4.2.5 總結

  1. 類變量是屬於類的變量,這個類的因此實例能夠共享這個變量
  2. 對象(實例或者類),能夠動態的給本身增長一個屬性(賦值即定義)
  3. 實例.__dict__[變量名] 和 實例.變量名 均可以訪問到實例本身的屬性
  4. 實例的同名變量會隱藏掉類變量,或者說是覆蓋了這個類變量。可是注意類變量還在那裏,並無真正被覆蓋。
  5. 實例屬性查找順序:指的是實例使用.點號來訪問屬性,會先找本身的__dict__,若是沒有,而後經過屬性__class__找到本身的類,而後再在類的__dict__中查找。直接使用實例.__dict__[變量名],不會在類中查找。
  6. 通常來講類變量能夠所有用大寫來表示(當常量用)

4.3 裝飾一個類

爲一個類經過裝飾,增長一些類屬性。例如可否給一個類增長一個NAME類屬性並提供屬性值

def dec(name):
    def warpper(cls):
        cls.NAME = name
        return cls
    return warpper


@dec('tom')  # Person = dec('tom')(Person)
class Person:
    pass

a = Person()
print(a.NAME)

類也能夠做爲一個裝飾器,後面會說

能夠給類加裝飾器,那麼能夠給類中的方法加裝飾器嗎?答案固然是能夠的。

4.4 類方法和靜態方法

        在類中中定義的方法大多都須要傳入一個self參數,用於指定一個實例化對象,用於經過對象來調用這個方法,可是也有例外的狀況

4.4.1 普通函數(不用)

        定義在類內部的普通函數,只能經過類來進行調用

In [23]: class Person: 
    ...:     def normal_function(): 
    ...:         print('normal_function') 
    ...:                                                                                                

In [24]: Person.normal_function()                                                                       
normal_function

In [25]: test = Person()                                                                                

In [27]: test.normal_function()                                                                         
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-27-9011f04ffcaa> in <module>
----> 1 test.normal_function()

TypeError: normal_function() takes 0 positional arguments but 1 was given

In [28]:

注意:

  1. normal_function 定義在類裏,只是受Person類管轄的一個普通函數而已,在Person中,認爲normal_function僅僅是Person的一個屬性而已.
  2. 當經過實例訪問時,因爲實例會默認把當前實例看成self傳入函數,而函數並無形參接受因此會報錯。

    雖然能夠經過類調用,可是沒人這樣用,也就是說是禁止這麼寫的

4.4.2 類方法

        類方法是給類用的,在使用時會將類自己當作參數傳給類方法的第一個參數,使用@classmethod裝飾器來把類中的某個函數定義成類方法。

In [31]: class Person: 
    ...:     @classmethod 
    ...:     def class_method(cls): 
    ...:         print('class={0.__name__}({0})'.format(cls)) 
    ...:         cls.HEIGHT=170 
    ...:                                                                                                

In [32]: Person.class_method()                                                                          
class=Person(<class '__main__.Person'>)

In [35]: daxin = Person()                                                                               

In [36]: daxin.class_method()                                                                           
class=Person(<class '__main__.Person'>)

In [37]:

注意:

  1. 在類定義中,使用@classmethod裝飾器的方法
  2. 必須至少有一個參數,且第一個參數留給了cls,cls指代調用者即類對象自身
  3. cls這個標識符能夠是任意合法名稱,可是爲了易讀,不建議修改
  4. 經過cls能夠直接操做類的屬性
  5. 經過類的實例依舊能夠調用類方法,那是由於當經過實例調用時,@classmethod發現調用者是實例時,會把當前實例的__class__對象,傳入給類方法的cls。

    類方法相似於C++、Java中的靜態方法。

4.4.3 靜態方法(用的不多)

        靜態方法是一種普通函數,位於類定義的命名空間中,不會對任何實例類型進行操做,它有以下特色

  1. 在類定義中,使用@classmethod裝飾器修飾的方法,在調用時不會隱式的傳入參數
  2. 靜態方法,只代表這個方法屬於這個名稱空間,函數歸在一塊兒,方便組織管理。
  3. 能夠理解爲一個函數封裝,能夠經過類調用、也能夠經過實例調用,只是強制的放在類中而已。
class Person:
    @staticmethod
    def static_method():
        print('static_method')


daxin = Person()
Person.static_method()
daxin.static_method()

和普通函數的區別是,靜態方法可使用實例調用,普通方法不能夠。

4.4.4 方法的調用

        類幾乎能夠調用全部方法,普通函數的調用通常不可能出現,由於不容許這麼定義。實例也幾乎能夠調用全部內部定義的方法,可是調用普通方法時會報錯,緣由是第一參數必須是類的實例self。

class Test:
    age = 10
    def normal_funtion():
        print('normal_function')

    @classmethod
    def class_method(cls):
        print(cls.__name__)

    @staticmethod
    def static_method():
        # Test.age
        print('static_method')

daxin = Test()
daxin.normal_funtion()   # 沒法調用,由於經過實例調用時,實例會被看成第一個參數傳給normal_funcion。
daxin.class_method()     # 能夠調用,當@classmethod檢測到是經過實例調用時,會把當前實例的__class__,看成cls傳遞,能夠訪問類屬性,但沒法訪問實例屬性
daxin.static_method()    # 能夠調用,當@staticmethod檢測到是經過實例調用時,會在當前實例的類中調用靜態方法,沒法訪問類或實例的屬性,可是能夠訪問全局變量(好比全局變量Test類)

Test.normal_funtion()    # 能夠調用,類調用時不傳遞參數,恰好normal_function也不須要參數,沒法訪問類或實例的屬性
Test.class_method()      # 能夠調用,類方法,經過類調用時,類會被看成cls傳遞給函數,能夠訪問類屬性,沒法訪問實例屬性
Test.static_method()     # 能夠調用,靜態方法,等於定義在類內的函數,沒法訪問類或實例的屬性,可是能夠訪問全局變量(好比全局變量Test類)

注意:

  1. 實例除了普通方法均可以調用,普通方法須要對象的實例做爲第一參數。
  2. 類能夠調用全部類中定義的方法(包括類方法、靜態方法), 普通方法不用傳遞參數,靜態方法,和類方法須要查找到實例的類。

體會:

class Test:
    age = 10
    def normal_funtion(abc):
        print('{}'.format(abc))

Test.normal_funtion(Test)
Test.normal_funtion(123)
a = Test()
a.normal_funtion()

        normal_function依舊是一個普通方法而已,加了參數發現類和實例均可以調用了,那是由於以前實例沒法調用是由於實例調用時默認傳遞給函數做爲第一個參數,那麼我給普通函數加一個形參接受就能夠了。沒有場景這樣使用,這裏只作學習瞭解。

5 訪問控制

        封裝成類還能夠控制什麼屬性可讓別人訪問,什麼方法不能讓別人訪問,這就叫作訪問控制。

5.1 私有屬性

使用 雙下劃線開頭 的屬性名,就是私有屬性,現有以下例子:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age 

daxin = Person('daxin', 18)
print(daxin.age)

        咱們能夠在外面直接使用daxin.age來訪問daxin的age屬性,可是若是這個age是銀行卡密碼,不能讓別人知道該怎麼辦呢,Python提供了一種私有屬性來解決

class Person:
    def __init__(self, name, age):
        self.name = name
        self.__age = age 
        
daxin = Person('daxin', 20)
print(daxin.__age)

        使用__開頭的屬性被稱爲私有屬性,這種屬性在類的外部是沒法直接進行訪問的,前面所說__dict__中會存放類的屬性信息,那麼咱們來看一下daxin實例的__dict__是怎麼樣的。

print(daxin.__dict__)

{'name': 'daxin', '_Person__age': 20}

根據上面結果能夠得知:私有屬性的本質上實際上是屬性更名,設置self.__age屬性時,__age 變爲了 _Person__age(_類名__屬性名)

class Person:
    def __init__(self,name, age):
        self.name = name
        self.__age = age

daxin = Person('daxin',18)
daxin.__age = 100
print(daxin.__dict__) # {'name': 'daxin', '_Person__age': 18, '__age': 100}

        只有在類中定義的私有變量纔會被更名,上面咱們雖然指定了實例的變量__age,但因爲是在類外定義的,因此它並不會變形,就真的產生了一個__age屬性,而在類內定義的,因爲變型了,因此不會覆蓋。觀察__dict__就能夠看出結果。

        咱們知道私有屬性在定義時會被更名,而且知道更名後的屬性名稱,那麼咱們是否就能夠修改了呢?

class Person:
    def __init__(self,name, age):
        self.name = name
        self.__age = age
    def get_age(self):
        return self.__age
daxin = Person('daxin',18)
daxin._Person__age = 100
print(daxin.get_age())   # 100

經過結果咱們能夠知道,只要知道了私有變量的名稱,就能夠直接從外部訪問,而且修改它。但並不建議這麼作!

Java等其餘語言,比較嚴格,私有在外面是絕對訪問不到的。

5.2 保護變量

        保護變量(protected),其實Python並不支持保護變量,是開發者本身的不成文的約定。那什麼是保護變量呢?在變量前使用 一個下劃線 的變量稱爲保護變量。

class Person:
    def __init__(self,name):
        self._name = name

daxin = Person('daxin')
print(daxin.__dict__)  # {'_name': 'daxin'}
print(daxin._name)   # daxin

註釋:

  1. 保護變量不會被改變名稱
  2. 外部依舊能夠看到而且調用

    若是看見這種變量,就如同私有變量,儘可能不要直接使用

5.3 私有方法

        前面說了私有屬性,那麼私有方法和私有屬性是類似的,使用單/雙下劃線開頭的方法稱爲私有方法(不能已雙下劃線結尾)

class Person:
    def __init__(self,name):
        self._name = name

    def __age(self):
        return self._name

print(Person.__dict__)  # {'__module__': '__main__', '__init__': <function Person.__init__ at 0x00000228BCB22158>, '_Person__age': <function Person.__age at 0x00000228BCB221E0>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}

經過上面__dict__的內容,咱們發現私有方法和私有屬性是相同的,都會被更名,因此知道了私有方法真正的名字,咱們依舊能夠在外部進行調用,可是不建議。

單下劃線的方法,也和變量相同,解釋器不會作任何變型,只是告訴你,它是一個私有方法,不建議直接使用。

5.4 補丁:(黑科技)

        能夠經過修改或替換類的成員。使用者調用的方式沒有改變,可是,類提供的功能可能已經改變了。(好比前面的類裝飾器),一般稱做猴子補丁(Monkey Patch)

        在運行時,對屬性、方法、函數等進行動態替換。其目的每每是爲了經過替換、修改來加強、擴展原有代碼的能力。

class Person:

    def __init__(self, name):
        self.name = name

    def sing(self):
        print('{} is sing'.format(self.name))


def monkeypatch():    # 猴子補丁,用於動態修改Person中某個方法
    Person.sing = lambda self: print('hello world')    # 能夠來自於不一樣包中的某個函數,這裏只是使用lambda舉例

monkeypatch()    # 執行後,Person.sing函數就被替換掉了

daxin = Person('daxin')
daxin.sing()   # 'hello world'

        通常狀況下是不建議使用的,可是在某些場景下,好比我替換的方法原來是鏈接數據庫獲取數據的,反覆鏈接數據庫測試不是很方便,因此在這種狀況下,咱們使用補丁的方式,返回部分目標數據就行了,不用每次都去數據庫取數據。

5.5 屬性裝飾器

        被屬性裝飾器裝飾的方法,就變成了屬性了。是否是很拗口?簡單來講就是:屬性裝飾器把一個方法變成屬性,進行調用。這麼作的目的能夠把某些屬性保護起來,不讓外部訪問(經過前面私有屬性的瞭解,咱們知道這是不可能的,嘿嘿嘿)。它主要由下面三種組成,能夠組合使用也能夠單獨使用。

  1. @property:標識下面的方法爲屬性方法,同時激活setter,deleter
  2. @方法.setter:設置屬性時調用方法
  3. @方法.deleter:刪除屬性時調用方法

不使用屬性裝飾器時,咱們爲了隱藏某個屬性可使用以下方法:

class Person:

    def __init__(self, name, age=18):
        self.name = name
        self.__age = age

    def age(self):    # 通常稱爲getter方法
        return self.__age

    def setage(self, value):  # 通常稱爲setter方法
        self.__age = value

daxin = Person('daxin')
daxin.setage(30)
print(daxin.age())

        使用起來很彆扭,爲何獲取age屬性要加括號執行方法呢?如何能讓用戶在使用age或者setage時不要認爲他們是在調用函數,而是一個屬性呢,下面使用屬性裝飾器property來完成這個需求

class Person:

    def __init__(self, name, age=18):
        self.name = name
        self.__age = age

    @property
    def age(self):    # 通常稱爲getter方法
        return self.__age
        
daxin = Person('daxin')
print(daxin.age)

        添加了@property裝飾器之後,被他裝飾的函數,就能夠像普通的屬性來訪問了(daxin.age,就是daxin.age()的返回值),那是否可使用daxin.age=100來設置age屬性的值呢?

daxin.age = 100 
>> AttributeError: can't set attribute

        沒法設置的,是由於property裝飾的函數是隻讀的,若是要使用daxin.age = 100 來賦值時,還須要一個setter裝飾器來裝飾一個設置屬性的函數,而且這個函數必須和property裝飾的函數的名稱相同。那若是要刪除呢,天然就觸發了deleter裝飾器了,咱們在方法中,刪除屬性便可。

class Person:

    def __init__(self, name, age=18):
        self.name = name
        self.__age = age
        
    @property
    def age(self):    # 通常稱爲getter方法
        return self.__age
    
    @age.setter    # 被裝飾的函數.setter(設置一個屬性的值時觸發)
    def age(self,value):
        self.__age = value 
    
    @age.deleter   # 刪除一個屬性時觸發(很不經常使用)
    def age(self):
        del self.__age

daxin = Person()
print(daxin.age) # 觸發@property裝飾過的age
daxin.age = 200  # 觸發age.setter 
del daxin.age    # 觸發age.deleter

這樣使用起來就比較方便了,固然property還提供了另外一種寫法property(getter,settr,deleter,'description'),由於property其實是一個class類而已。

class Person:
    def __init__(self, name, age=18):
        self.name = name 
        self.__age = age 
    
    def getage(self):
        return self.__age 
        
    def setage(self, value):
        self.__age = value 
    def delage(self):
        del self.__age 
        
    age = property(getage,setage,delage,'age property')

例:快速實現一個只讀屬性

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age 
        
    age = property(lambda self:self.__age)  # 快捷包裝一個只讀屬性。

由於lambda不支持等號,而setter中有賦值等號,因此lambda就沒法實現這個需求了。

5.6 對象的銷燬

        __init__ 用於在對象被實例化時爲對象初始化一些屬性信息,按照其餘語言的邏輯,也能夠稱之爲構造器,既然有構造器,那麼就會有析構器,即在對象被銷燬時執行。在Python的類中使用__del__方法定義的函數稱爲析構函數,在對象被銷燬時觸發執行。須要注意的是,這個方法不能引發對象自己的銷燬,只是對象銷燬的時候會自動調用它。換言之,就是當對象引用計數爲0時,觸發銷燬操做(標記爲可回收,等待GC),而銷燬操做又會觸發對象的__del__方法。

class Person:
    def __init__(self, name):
        self.name = name

    def __del__(self):
        print('{} is die'.format(self.name))

daxin = Person('daxin')
daxin.__del__()    # 僅僅是執行__del__方法,不會真正銷燬
daxin.__del__()    # 僅僅是執行__del__方法,不會真正銷燬
del daxin   # 在本例中引用計數爲0,即真正的銷燬


# 代碼執行完畢後也會觸發銷燬操做。

通常在析構函數中清理當前實例中申請的內存空間或者某些對象,作一些資源釋放的工做。

5.7 方法重載(overload)

        在其餘面向對象的高級語言中,會有重載的改變。所謂重載,就是同一個方法名,可是參數數量、類型不同,就是同一個方法的重載。

下面是一段模擬其餘語言方法重載的僞代碼

func test(x int, y int) {
    pass
}
    
func test(x array,y array) {
    pass 
}
    
test(4,5)
test([1],[2])

        在其餘語言中,當test(4,5)時會調用上面的函數,當test([1],[2])會調用下面的函數,這就叫作類型重載,傳遞不一樣的參數類型,就會執行對應的方法,而在Python中,後面的會直接覆蓋前面的同名函數,因此Python沒有重載,固然Python也不須要重載,爲何呢?由於Python的動態語言的特性,其實悄悄的就實現了其餘語言的類型重載

def test(x,y):
    return x + y 
    
test(4,5)
test([1],[2])
test('a','b')

        在Python中上面是能夠執行的,可是在其餘語言中,可能就須要對應三個函數處理不一樣類型的數據,而後經過類型重載來調用。因爲Python語言的特性,天生就能實現類型重載的功能。

相關文章
相關標籤/搜索