python基礎學習之描述符和裝飾器

  • 描述符的瞭解:python

    描述符協議:閉包

    python描述符是一個「綁定行爲」的對象屬性,在描述符協議中,它能夠經過方法重寫屬性的訪問。這些方法有:函數

         __get__, __set__, 和__delete__測試

    若是這些方法中的任何一個被定義在一個對對象

    象中,這個對象就是一個描述符
  • 總結下說人話:就是當一個類中實例化另外一個類的時候,經過本類來調用另外一個類的功能,控制實例對象 訪問 這個屬性作一些額外的操做遞歸

    class Fee:       #先定義一個Fee類
        def __get__(self, instance, owner):
            print('get:疏樓龍宿人間至帥')
        def __set__(self, instance, value):
            print('set:疏樓龍宿%s'%value)
        def __delete__(self, instance):
            print('delete:疏樓龍宿人間無敵')get

    class Fdd:      #在定義一個類Fdd
        fee = Fee()    #咱們在Fdd中實例化Feeclass

  • 咱們能夠經過Fdd實例化來調用Fee中功能,如何實現呢?test

  • a = Fdd()     #先實例化,接下來咱們須要瞭解功能的對應調用狀況import

    a.fee       # 即調用fee屬性,fee屬性是什麼,就是Fee實例化,這時候會觸發__get__功能

    a.fee = '人間至帥'             #即對fee屬性進行修改,會觸發類Fee中的__set__功能,將修改內容傳入參數value

    del a.fee         # 刪除a實例化中fee屬性,就是要刪掉Fee(),會觸發Fee類中__delete__屬性

裝飾器:重點啊重點啊重點啊!

    • 什麼是裝飾器?

      裝飾器本質上也是函數,他是用來修飾其餘函數,給其餘函數添加額外功能的

    • 裝飾器原則:

      1--不改變被修飾函數的源代碼

      2--不改變被修飾函數的調用方式

    • 裝飾器的本質是什麼?

      高階函數+函數嵌套+閉包

    • 咱們回顧下,什麼是高階函數?

      --函數接受的參數是另外一個函數的函數名

      --函數的返回值另外一個函數的函數名

      知足上述兩個條件中的一個,能夠稱爲高階函數

      【注意:若是返回值是這個函數自己的調用方式,那麼這是遞歸!】

    • 咱們再回顧下,什麼閉包?

      就是函數的返回值是其內部嵌套函數的函數名

    • 按照上述的規則,咱們來推導下裝飾器應該是什麼樣子

    • 好比,咱們須要測試一下isinstance()這個函數的運行時間

      首先,根據原則要求,咱們不能改變被修飾函數的源代碼,也就是說,isinstance()函數寫完後要放在一邊,咱們先寫一個運行代碼:

    • def test_isinstance():
          for i in range(0,100000):
              if isinstance(i,int) is True:
                  pass
          pass

      因爲一次運行的時間過短,咱們運行10W次,

      按照原則1,這個時候,這個代碼就不能動了,咱們應該如何測試他的時間呢?

    • 導入時間模塊包,寫一段高階函數,將上述函數傳入其中做爲參數,在運行前,記錄下時間,而後運行該函數,運行完,再記錄下時間,作個差運算,得出時間差,即運行時間

      import time
      def test_time(fun):
              star_time = time.time()
              fun()
              stop_time = time.time()
              run_time = stop_time - star_time
          print('運行時間是%s' %run_time)

    • OK,咱們已經完美的得出了運行時間,並且源代碼也沒動

    • 那麼問題來,函數調用方式,按照test_isinstance()的方式調用,咱們沒有獲得運行時間。。。。。若是要同時獲得結果,咱們就要使用test_time(test_isinstance)這個方法,和原則2衝突

    • 到目前爲止,咱們已經解決了需求問題,即測試代碼運行時間,那麼若是進一步使調用方式不變呢?咱們想到了高階函數中的返回函數名,若是我返回值是須要測試的函數的函數名呢?

    • 代碼:

      def test_isinstance():
          for i in range(0,100000):
              if isinstance(i,int) is True:
                  pass
          pass

      import time
      def test_time(fun):
          star_time = time.time()
          fun()
          stop_time = time.time()
          run_time = stop_time - star_time
          print('運行時間是%s' %run_time)

          return test_isinstance

    • 這個時候,咱們再作以下操做:

      test_isinstance = test_time(test_isinstance)

      問題彷佛解決了,我直接使用就能夠獲得結果

    • 至此,咱們已經獲得了 裝飾器的雛形,至於最後一部複製問題,裝飾器的格式,能夠解決,又稱爲 語法糖

      @test_time

      def test_isinstance():

          .......

          .......

      最開的時候@test_time的功能效果便是:

      test_isinstance = test_time(test_isinstance)

    • 仔細檢查後,發現咱們彷佛運行了兩次test_isinstance函數?

      test_time中運行了一次,接受返回函數名後咱們加上括號,又運行了一次,問題來了,這下怎麼辦?

    • 遇到問題,解決問題,多運行了一次,咱們看看能不能少運行一次

      常規手段沒法達成目的,咱們回憶下裝飾器的的本質是什麼

      高階函數(已有)+函數嵌套(無)+閉包(無)

      咱們還有其餘手段能夠用,好比函數嵌套,和閉包

    • 咱們要實現的終極目的是test_isinstance()的同時會讓上述的兩個函數都運行一次,且只運行一次,並且,要得到原函數結果,上述test_time函數已經能夠得到時間,同時喊運行了一次test_isinstance函數,若是咱們截取下他內部test_isinstance的運行結果,做爲本函數的返回值呢?

      import time

      def test_time(fun):
          star_time = time.time()
          res = fun()
          stop_time = time.time()
          run_time = stop_time - star_time
          print('運行時間是%s' %run_time)

          return res

      咱們能夠發現,運行這個函數,咱們能夠獲得和運行test_isinstance函數同樣的結果,由於返回值就是他的運行結果

    • 咱們彷佛離真相愈來愈近了

    • 如同上面說的同樣,咱們須要test_isinstance()的時候,讓函數運行一次,而不是兩次,函數運行是加括號直接運行,少運行一次,咱們就要少一個括號,即test_isintance的時候不運行,test_isintance上面咱們等於了 test_time(test_istntance),也便是,這個時候,我不運行函數,等加上括號的時候,在運行函數,兼顧上述咱們測試出來所須要的全部功能【增長附加功能,獲取本函數原返回值】

    • 通過思考和測試,使用閉包功能

          import time
          def test_time(fun):
              def test():
                          star_time = time.time()
                          res = fun()
                          stop_time = time.time()
                          run_time = stop_time - star_time
                  print('運行時間是%s' %run_time)
                  return res
              return test

    • 解釋代碼:

      在測試函數上掛上@test_time,即:test_isintance = test_time(test_isintance),這個時候,同等於test_isintance=test【由於裝飾器函數test_time的返回值是test,test爲內嵌函數】;當test_isintance加上括號()運行的時候,等於運行了test()而後會進行下面一系列操做,同時,會返回原函數的返回值。

    • 至此,裝飾器函數出爐了。

    • 增長問題:

      若是我函數自己就有參數呢。。。。。。。

      好比:test_isintance(1,2,3,4,5)

      這個等於什麼?不就等於 test(1,2,3,4,5) 麼!

      也就是說我內嵌函數中,有可能會有參數!代碼,須要普適性,這種狀況也須要考慮進去!參數形式能肯定麼?彷佛不能,一樣,咱們也不該該根據測試函數的參數特色再去修改函數。

    • 咱們須要把參數傳給,那麼函數參數形參和實參的賦值關係主要分爲哪幾類?

      位置參數、關鍵字參數,即*args **kwargs,咱們在test_isintance的括號內加上參數,也就是在test的括號內加上參數,多是0個,多是若干個,*args和**kwargs完美接收了上述參數,在本來的函數test_isintance()的參數傳給test()後,再由test在代碼塊內部傳給test_isintance()【即代碼中的fun()】

    • 最後咱們的代碼應該以下:

    • import time
      def test_time(fun):
          def test(*args,**kwargs):
                      star_time = time.time()
                      res = fun(*args,**kwargs)
                      stop_time = time.time()
                      run_time = stop_time - star_time
              print('運行時間是%s' %run_time)
              return res
          return test

    • 至此,裝飾器徹底體

    • 使用方式:在上述代碼已經提早寫好的狀況下,使用語法糖便可

    • @test_time

      def test_isinstance():    for i in range(0,100000):        if isinstance(i,int) is True:            pass    pass

相關文章
相關標籤/搜索