Python高級(jí)語(yǔ)法5:私有屬性、魔法屬性、with與上下文管理器

一、私有屬性

  • 我們大家知道在類(lèi)里面定義的屬性名前加兩個(gè)下劃線就是私有屬性,它是不能在外面被訪問(wèn)的,如下:

    class Person(object):
    
          def __init__(self,name):
                self.__name = name
    
    person = Person("小王")
    print(person.__name)
    

    打印結(jié)果:報(bào)錯(cuò),__name是私有的,外面是無(wú)法訪問(wèn)的

    AttributeError: 'Person' object has no attribute '__name'
    

    分析上面報(bào)錯(cuò)的原因是:__name被Python改了名字,一種假象而已,我們可以看看Python把它改成了什么,我們通過(guò)魔法屬性 person.__dict__ 來(lái)打印

    print(person.__dict__)
    

    打印結(jié)果是:

    {'_Person__name': '小王'}
    

    看到上面的打印,其實(shí)Python是把私有的屬性改成了 一個(gè)下劃線+類(lèi)名+私有屬性_類(lèi)名私有屬性,如上面的__name -> _Person__name,那么我們就可以通過(guò) 實(shí)例對(duì)象.被改后的私有屬性名 來(lái)打印私有屬性,如下

    print(person._Person__name)
    

    打印結(jié)果是:小王

二、魔法屬性

無(wú)論人或事物往往都有不按套路出牌的情況,Python的類(lèi)屬性也是如此,存在著一些具有特殊含義的屬性,詳情如下:

  • 2.1、__doc__ : 表示類(lèi)的描述信息

    class Foo:
         """ 描述類(lèi)信息,這是用于看片的神奇 """
         def func(self):
               pass
    
    print(Foo.__doc__)
    #輸出:類(lèi)的描述信息
    
  • 2.2、__module____class__

    • __module__ 表示當(dāng)前操作的對(duì)象在那個(gè)模塊

    • __class__ 表示當(dāng)前操作的對(duì)象的類(lèi)是什么,也就是打印類(lèi)對(duì)象的名字

    • test.py

      class Person(object):
             def __init__(self):
                  self.name = 'laowang'
      
    • main.py

      from test import Person
      
      obj = Person()
      print(obj.__module__)  # 輸出 test 即:輸出模塊
      print(obj.__class__)  # 輸出 test.Person 即:輸出類(lèi)
      
  • 2.3、 __init__ : 初始化方法,通過(guò)類(lèi)創(chuàng)建對(duì)象時(shí),自動(dòng)觸發(fā)執(zhí)行

    class Person:
          def __init__(self, name):
                self.name = name
                self.age = 18
    
    obj = Person('laowang')  # 自動(dòng)執(zhí)行類(lèi)中的 __init__ 方法
    
  • 2.4、__del__ :當(dāng)對(duì)象在內(nèi)存中被釋放時(shí),自動(dòng)觸發(fā)執(zhí)行。
    提示:此方法一般無(wú)須定義,因?yàn)镻ython是一門(mén)高級(jí)語(yǔ)言,程序員在使用時(shí)無(wú)需關(guān)心內(nèi)存的分配和釋放,因?yàn)榇斯ぷ鞫际墙唤oPython解釋器來(lái)執(zhí)行,所以,__del__的調(diào)用是由解釋器在進(jìn)行垃圾回收時(shí)自動(dòng)觸發(fā)執(zhí)行的。

    class Foo:
          def __del__(self):
                pass
    
  • 2.5、__call__ : 對(duì)象后面加括號(hào),觸發(fā)執(zhí)行
    提示:__init__方法的執(zhí)行是由創(chuàng)建對(duì)象觸發(fā)的,即:對(duì)象 = 類(lèi)名();而對(duì)于__call__方法的執(zhí)行是由對(duì)象后加括號(hào)觸發(fā)的,即:對(duì)象() 或者 類(lèi)()()

    class Foo:
          def __init__(self):
                 pass
    
          def __call__(self, *args, **kwargs):
                 print('__call__')
    
    
    obj = Foo()  # 執(zhí)行 __init__
    obj()  # 執(zhí)行 __call__
    
  • 2.6、__dict__ : 類(lèi)或?qū)ο笾械乃袑傩?類(lèi)的實(shí)例屬性屬于對(duì)象;類(lèi)中的類(lèi)屬性和方法等屬于類(lèi)

    class Province(object):
            country = 'China'
    
            def __init__(self, name, count):
                 self.name = name
                 self.count = count
    
            def func(self, *args, **kwargs):
                 print('func')
    
    # 獲取類(lèi)的屬性,即:類(lèi)屬性、方法、
    print(Province.__dict__)
    # 輸出:{'__dict__': <attribute '__dict__' of 'Province' objects>, '__module__': '__main__', 'country': 'China', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Province' objects>, 'func': <function Province.func at 0x101897950>, '__init__': <function Province.__init__ at 0x1018978c8>}
    
    obj1 = Province('山東', 10000)
    print(obj1.__dict__)
    # 獲取 對(duì)象obj1 的屬性
    # 輸出:{'count': 10000, 'name': '山東'}
    
    obj2 = Province('山西', 20000)
    print(obj2.__dict__)
    # 獲取 對(duì)象obj1 的屬性
    # 輸出:{'count': 20000, 'name': '山西'}
    
  • 2.7、__str__: 如果一個(gè)類(lèi)中定義了 __str__ 方法,那么在打印 對(duì)象 時(shí),默認(rèn)輸出該方法的返回值

    class Foo:
          def __str__(self):
                return '小李'
    
    obj = Foo()
    print(obj)
    # 輸出:小李
    
  • 2.8、__getitem__、__setitem____delitem__ : 用于索引操作,如字典。以上分別表示 獲取、設(shè)置、刪除 數(shù)據(jù)

    class Foo(object):
    
        def __getitem__(self, key):
               print('__getitem__', key)
    
        def __setitem__(self, key, value):
               print('__setitem__', key, value)
    
        def __delitem__(self, key):
               print('__delitem__', key)
    
    
    obj = Foo()
    
    result = obj['k1']      # 自動(dòng)觸發(fā)執(zhí)行 __getitem__
    obj['k2'] = 'laowang'   # 自動(dòng)觸發(fā)執(zhí)行 __setitem__
    del obj['k1']           # 自動(dòng)觸發(fā)執(zhí)行 __delitem__
    
  • 2.9、__getslice__、__setslice__、__delslice__: 該三個(gè)方法用于分片操作,如:列表

    class Foo(object):
    
         def __getslice__(self, i, j):
                print('__getslice__', i, j)
    
         def __setslice__(self, i, j, sequence):
                print('__setslice__', i, j)
    
         def __delslice__(self, i, j):
                print('__delslice__', i, j)
    
    obj = Foo()
    
    obj[-1:1]                   # 自動(dòng)觸發(fā)執(zhí)行 __getslice__
    obj[0:1] = [11,22,33,44]    # 自動(dòng)觸發(fā)執(zhí)行 __setslice__
    del obj[0:2]                # 自動(dòng)觸發(fā)執(zhí)行 __delslice__
    

三、with與上下文管理器”

  • 3.1、對(duì)于 系統(tǒng)資源如文件、數(shù)據(jù)庫(kù)連接、socket 而言,應(yīng)用程序打開(kāi)這些資源并執(zhí)行完業(yè)務(wù)邏輯之后,必須做的一件事就是要關(guān)閉(斷開(kāi))該資源。
    比如 Python 程序打開(kāi)一個(gè)文件,往文件中寫(xiě)內(nèi)容,寫(xiě)完之后,就要關(guān)閉該文件,否則會(huì)出現(xiàn)什么情況呢?極端情況下會(huì)出現(xiàn) "Too many open files" 的錯(cuò)誤,因?yàn)橄到y(tǒng)允許你打開(kāi)的最大文件數(shù)量是有限的。

    同樣,對(duì)于數(shù)據(jù)庫(kù),如果連接數(shù)過(guò)多而沒(méi)有及時(shí)關(guān)閉的話,就可能會(huì)出現(xiàn) "Can not connect to MySQL server Too many connections",因?yàn)閿?shù)據(jù)庫(kù)連接是一種非常昂貴的資源,不可能無(wú)限制的被創(chuàng)建。

  • 3.2、看看如何正確關(guān)閉一個(gè)文件。

    • 普通版:打開(kāi)文件之后直接進(jìn)行讀寫(xiě)操作

      def m1():
           f = open("output.txt", "w")
           f.write("python之禪")
           f.close()
      

      這樣寫(xiě)有一個(gè)潛在的問(wèn)題,如果在調(diào)用 write 的過(guò)程中,出現(xiàn)了異常進(jìn)而導(dǎo)致后續(xù)代碼無(wú)法繼續(xù)執(zhí)行,close 方法無(wú)法被正常調(diào)用,因此資源就會(huì)一直被該程序占用者釋放。那么該如何改進(jìn)代碼呢?

    • 進(jìn)階版: 看著不夠簡(jiǎn)潔哈,后面還有高級(jí)版

      def m2():
           f = open("output.txt", "w")
           try:
                f.write("python之禪")
           except IOError:
                print("oops error")
           finally:
                f.close()
      

      改良版本的程序是對(duì)可能發(fā)生異常的代碼處進(jìn)行 try 捕獲,使用 try/finally 語(yǔ)句,該語(yǔ)句表示如果在 try 代碼塊中程序出現(xiàn)了異常,后續(xù)代碼就不再執(zhí)行,而直接跳轉(zhuǎn)到 except 代碼塊。而無(wú)論如何,finally 塊的代碼最終都會(huì)被執(zhí)行。因此,只要把 close 放在 finally 代碼中,文件就一定會(huì)關(guān)閉。

    • 高級(jí)版: 這么看起來(lái)就非常爽了

      def m3():
           with open("output.txt", "r") as f:
                  f.write("Python之禪")
      

      一種更加簡(jiǎn)潔、優(yōu)雅的方式就是用 with 關(guān)鍵字。open 方法的返回值賦值給變量 f,當(dāng)離開(kāi) with 代碼塊的時(shí)候,系統(tǒng)會(huì)自動(dòng)調(diào)用 f.close() 方法, with 的作用和使用 try/finally 語(yǔ)句是一樣的。那么它的實(shí)現(xiàn)原理是什么?在講 with 的原理前要涉及到另外一個(gè)概念,就是上下文管理器(Context Manager)。

  • 3.3、上下文管理器(Context Manager)

    • 什么是上下文(context)?

      • 看,一篇文章,給你摘錄一段,沒(méi)前沒(méi)后,你讀不懂,因?yàn)橛姓Z(yǔ)境,就是語(yǔ)言環(huán)境存在,一段話說(shuō)了什么,要通過(guò)上下文(文章的上下文)來(lái)推斷。
      • app點(diǎn)擊一個(gè)按鈕進(jìn)入一個(gè)新的界面,也要保存你是在哪個(gè)屏幕跳過(guò)來(lái)的等等信息,以便你點(diǎn)擊返回的時(shí)候能正確跳回,如果不存肯定就無(wú)法正確跳回了。
      • 看這些都是上下文的典型例子,理解成環(huán)境就可以,(而且上下文雖然叫上下文,但是程序里面一般都只有上文而已,只是叫的好聽(tīng)叫上下文。。進(jìn)程中斷在操作系統(tǒng)中是有上有下的,不過(guò)不這個(gè)高深的問(wèn)題就不要深究了。。。)
    • 上下文管理器
      任何實(shí)現(xiàn)了 __enter__()__exit__() 方法的對(duì)象都可稱(chēng)之為上下文管理器,上下文管理器對(duì)象可以使用 with 關(guān)鍵字。顯然,文件(file)對(duì)象也實(shí)現(xiàn)了上下文管理器。
      那么文件對(duì)象是如何實(shí)現(xiàn)這兩個(gè)方法的呢?我們可以模擬實(shí)現(xiàn)一個(gè)自己的文件類(lèi),讓該類(lèi)實(shí)現(xiàn) __enter__()__exit__()方法。

      class File():
      
           def __init__(self, filename, mode):
                  self.filename = filename
                  self.mode = mode
      
           def __enter__(self):
                  print("entering")
                  self.f = open(self.filename, self.mode)
                  return self.f
      
           def __exit__(self, *args):
                  print("will exit")
                  self.f.close()
      

      __enter__()方法返回資源對(duì)象,這里就是你將要打開(kāi)的那個(gè)文件對(duì)象,__exit__()方法處理一些清除工作。
      因?yàn)?File 類(lèi)實(shí)現(xiàn)了上下文管理器,現(xiàn)在就可以使用 with 語(yǔ)句了。

      with File('out.txt', 'w') as f:
               print("writing")
               f.write('hello, python')
      

      提示:File('out.txt', 'w')去執(zhí)行的時(shí)候就會(huì)去調(diào)用里面的_enter__方法返回一個(gè)操作文本的對(duì)象,當(dāng)在進(jìn)行寫(xiě)的操作的時(shí)候,如果出現(xiàn)了異常,會(huì)自動(dòng)調(diào)用__exit__,釋放響應(yīng)的資源,這樣,你就無(wú)需顯示地調(diào)用 close 方法了,由系統(tǒng)自動(dòng)去調(diào)用,哪怕中間遇到異常 close 方法也會(huì)被調(diào)用。

  • 3.4、實(shí)現(xiàn)上下文管理器的另外方式
    Python 還提供了一個(gè) contextmanager 的裝飾器,更進(jìn)一步簡(jiǎn)化了上下文管理器的實(shí)現(xiàn)方式。通過(guò) yield 將函數(shù)分割成兩部分,yield 之前的語(yǔ)句在 __enter__ 方法中執(zhí)行,yield 之后的語(yǔ)句在 __exit__ 方法中執(zhí)行。緊跟在 yield 后面的值是函數(shù)的返回值。

    from contextlib import contextmanager
    
    @contextmanager
    def my_open(path, mode):
          f = open(path, mode)
          yield f
          f.close()
    
    • 調(diào)用

      with my_open('out.txt', 'w') as f:
              f.write("hello , the simplest context manager")
      
    • 總結(jié): Python 提供了 with 語(yǔ)法用于簡(jiǎn)化資源操作的后續(xù)清除操作,是 try/finally 的替代方法,實(shí)現(xiàn)原理建立在上下文管理器之上。此外,Python 還提供了一個(gè) contextmanager 裝飾器,更進(jìn)一步簡(jiǎn)化上下管理器的實(shí)現(xiàn)方式。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 包(lib)、模塊(module) 在Python中,存在包和模塊兩個(gè)常見(jiàn)概念。 模塊:編寫(xiě)Python代碼的py...
    清清子衿木子水心閱讀 3,913評(píng)論 0 27
  • Scala與Java的關(guān)系 Scala與Java的關(guān)系是非常緊密的?。?因?yàn)镾cala是基于Java虛擬機(jī),也就是...
    燈火gg閱讀 3,608評(píng)論 1 24
  • 一、快捷鍵 ctr+b 執(zhí)行ctr+/ 單行注釋ctr+c ...
    o_8319閱讀 6,032評(píng)論 2 16
  • 關(guān)于端午節(jié)的由來(lái),有許多種說(shuō)法,有說(shuō)是紀(jì)念春秋時(shí)期伍子胥的,有說(shuō)是紀(jì)念孝女曹娥的,有說(shuō)起源于夏商周三代的夏至節(jié),有...
    阿果悠悠閱讀 308評(píng)論 0 4
  • 1.7日日精進(jìn):敬畏—進(jìn)入—體驗(yàn)—交給—持續(xù) 1,缺啥補(bǔ)啥,怕啥練啥; 2,一切為我所用,所用為團(tuán)隊(duì)家; 3,我...
    京心達(dá)張秀寶閱讀 162評(píng)論 0 0

友情鏈接更多精彩內(nèi)容