乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Python 黑魔法--- 描述器(descriptor)

       River_LaLaLa 2016-08-21


      來源:人世間

      鏈接:www.jianshu.com/p/250f0d305c35


      Python黑魔法,前面已經(jīng)介紹了兩個魔法,裝飾器和迭代器,通常還有個生成器。生成器固然也是一個很優(yōu)雅的魔法。生成器更像是函數(shù)的行為。而連接類行為和函數(shù)行為的時候,還有一個描述器魔法,也稱之為描述符。


      我們不止一次說過,Python的優(yōu)雅,很大程度在于如何設(shè)計成優(yōu)雅的API。黑魔法則是一大利器?;蛘哒fPython的優(yōu)雅很大程度上是建立在這些魔法巧技基礎(chǔ)上。


      何謂描述器


      當(dāng)定義迭代器的時候,描述是實現(xiàn)迭代協(xié)議的對象,即實現(xiàn)__iter__方法的對象。同理,所謂描述器,即實現(xiàn)了描述符協(xié)議,即__get__, __set__, 和 __delete__方法的對象。


      單看定義,還是比較抽象的。talk is cheap??创a吧:


      class WebFramework(object):

          def __init__(self, name='Flask'):

              self.name = name

       

          def __get__(self, instance, owner):

              return self.name

       

          def __set__(self, instance, value):

              self.name = value

       

       

      class PythonSite(object):

       

          webframework = WebFramework()

       

      In [1]: PythonSite.webframework

      Out[1]: 'Flask'

       

      In [2]: PythonSite.webframework = 'Tornado'

       

      In [3]: PythonSite.webframework

      Out[3]: 'Tornado'


      定義了一個類WebFramework,它實現(xiàn)了描述符協(xié)議__get__和__set__,該對象(類也是對象,一切都是對象)即成為了一個描述器。同時實現(xiàn)__get__和__set__的稱之為資料描述器(data descriptor)。僅僅實現(xiàn)__get__的則為非描述器。兩者的差別是相對于實例的字典的優(yōu)先級。


      如果實例字典中有與描述器同名的屬性,如果描述器是資料描述器,優(yōu)先使用資料描述器,如果是非資料描述器,優(yōu)先使用字典中的屬性。


      描述器的調(diào)用


      對于這類魔法,其調(diào)用方法往往不是直接使用的。例如裝飾器需要用 @ 符號調(diào)用。迭代器通常在迭代過程,或者使用 next 方法調(diào)用。描述器則比較簡單,對象屬性的時候會調(diào)用。


      In [15]: webframework = WebFramework()

       

      In [16]: webframework.__get__(webframework, WebFramework)

      Out[16]: 'Flask'


      描述器與對象屬性


      OOP的理論中,類的成員變量包括屬性和方法。那么在Python里什么是屬性?修改上面的PythonSite類如下:


      class PythonSite(object):

       

          webframework = WebFramework()

       

          version = 0.01

       

          def __init__(self, site):

              self.site = site


      這里增加了一個version的類屬性,以及一個實例屬性site。分別查看一下類和實例對象的屬性:


      In [1]: pysite = PythonSite('ghost')

       

      In [2]: vars(PythonSite).items()

      Out[2]:

      [('__module__', '__main__'),

      ('version', 0.01),

      ('__dict__', '__dict__' of 'PythonSite' objects>),

      ('webframework', <__main__.WebFramework at 0x10d55be90>),

      ('__weakref__', '__weakref__' of 'PythonSite' objects>),

      ('__doc__', None),

      ('__init__', __main__.__init__>)]

       

      In [3]: vars(pysite)

      Out[3]: {'site': 'ghost'}

      In [4]: PythonSite.__dict__

      Out[4]:

      {'__dict__': '__dict__' of 'PythonSite' objects>,

      '__doc__': None,

      '__init__': __main__.__init__>,

      '__module__': '__main__',

      '__weakref__': '__weakref__' of 'PythonSite' objects>,

      'version': 0.01,

      'webframework': <__main__.WebFramework at 0x10d55be90>}>


      vars方法用于查看對象的屬性,等價于對象的__dict__內(nèi)容。從上面的顯示結(jié)果,可以看到類PythonSite和實例pysite的屬性差別在于前者有 webframework,version兩個屬性,以及 __init__方法,后者僅有一個site屬性。


      類與實例的屬性


      類屬性可以使用對象和類訪問,多個實例對象共享一個類變量。但是只有類才能修改。


      In [6]: pysite1 = PythonSite('ghost')

       

      In [7]: pysite2 = PythonSite('admin')

       

      In [8]: PythonSite.version

      Out[8]: 0.01

       

      In [9]: pysite1.version

      Out[9]: 0.01

       

      In [10]: pysite2.version

      Out[10]: 0.01

       

      In [11]: pysite1.version is pysite2.version

      Out[11]: True

       

      In [12]: pysite1.version = 'pysite1'

       

      In [13]: vars(pysite1)

      Out[13]: {'site': 'ghost', 'version': 'pysite1'}

       

      In [14]: vars(pysite2)

      Out[14]: {'site': 'admin'}

       

      In [15]: PythonSite.version = 0.02

       

      In [16]: pysite1.version

      Out[16]: 'pysite1'

       

      In [17]: pysite2.version

      Out[17]: 0.02


      正如上面的代碼顯示,兩個實例對象都可以訪問version類屬性,并且是同一個類屬性。當(dāng)pysite1修改了version,實際上是給自己添加了一個version屬性。類屬性并沒有被改變。當(dāng)PythonSite改變了version屬性的時候,pysite2的該屬性也對應(yīng)被改變。


      屬性訪問的原理與描述器


      知道了屬性訪問的結(jié)果。這個結(jié)果都是基于Python的描述器實現(xiàn)的。通常,類或者實例通過.操作符訪問屬性。例如pysite1.site和pysite1.version的訪問。先訪問對象的__dict__,如果沒有再訪問類(或父類,元類除外)的__dict__。如果最后這個__dict__的對象是一個描述器,則會調(diào)用描述器的__get__方法。


      In [21]: pysite1.site

      Out[21]: 'ghost'

       

      In [22]: pysite1.__dict__['site']

      Out[22]: 'ghost'

       

      In [23]: pysite2.version

      Out[23]: 0.02

       

      In [24]: pysite2.__dict__['version']

      ---------------------------------------------------------------------------

      KeyError                                  Traceback (most recent call last)

      in ()

      ----> 1 pysite2.__dict__['version']

       

      KeyError: 'version'

       

      In [25]: type(pysite2).__dict__['version']

      Out[25]: 0.02

       

      In [32]: type(pysite1).__dict__['webframework']

      Out[32]: <__main__.WebFramework at 0x103426e90>

       

      In [38]: type(pysite1).__dict__['webframework'].__get__(None, PythonSite)

      Out[38]: 'Flask'


      實例方法,類方法,靜態(tài)方法與描述器


      調(diào)用描述器的時候,實際上會調(diào)用object.__getattribute__()。這取決于調(diào)用描述其器的是對象還是類,如果是對象obj.x,則會調(diào)用type(obj).__dict__['x'].__get__(obj, type(obj))。如果是類,class.x, 則會調(diào)用type(class).__dict__['x'].__get__(None, type(class)。


      這樣說還是比較抽象,下面來分析Python的方法,靜態(tài)方法和類方法。把PythonSite重構(gòu)一下:


      class PythonSite(object):

          webframework = WebFramework()

       

          version = 0.01

       

          def __init__(self, site):

              self.site = site

       

          def get_site(self):

              return self.site

       

          @classmethod

          def get_version(cls):

              return cls.version

       

          @staticmethod

          def find_version():

              return PythonSite.version


      類方法,@classmethod裝飾器


      先看類方法,類方法使用@classmethod裝飾器定義。經(jīng)過該裝飾器的方法是一個描述器。類和實例都可以調(diào)用類方法:


      In [1]: ps = PythonSite('ghost')

       

      In [2]: ps.get_version

      Out[2]: method type.get_version of '__main__.PythonSite'>>

       

      In [3]: ps.get_version()

      Out[3]: 0.01

       

      In [4]: PythonSite.get_version

      Out[4]: method type.get_version of '__main__.PythonSite'>>

       

      In [5]: PythonSite.get_version()

      Out[5]: 0.01


      get_version 是一個bound方法。下面再看下ps.get_version這個調(diào)用,會先查找它·的__dict__是否有g(shù)et_version這個屬性,如果沒有,則查找其類。


      In [6]: vars(ps)

      Out[6]: {'site': 'ghost'}

       

      In [7]: type(ps).__dict__['get_version']

      Out[7]: at 0x108952e18>

       

      In [8]: type(ps).__dict__['get_version'].__get__(ps, type(ps))

      Out[8]: method type.get_version of '__main__.PythonSite'>>

       

      In [9]: type(ps).__dict__['get_version'].__get__(ps, type(ps)) == ps.get_version

      Out[9]: True


      并且vars(ps)中,__dict__并沒有g(shù)et_version這個屬性,依據(jù)描述器協(xié)議,將會調(diào)用type(ps).__dict__['get_version']描述器的__get__方法,因為ps是實例,因此object.__getattribute__()會這樣調(diào)用__get__(obj, type(obj))。


      現(xiàn)在再看類方法的調(diào)用:


      In [10]: PythonSite.__dict__['get_version']

      Out[10]: at 0x108952e18>

       

      In [11]: PythonSite.__dict__['get_version'].__get__(None, PythonSite)

      Out[11]: method type.get_version of '__main__.PythonSite'>>

       

      In [12]: PythonSite.__dict__['get_version'].__get__(None, PythonSite) == PythonSite.get_version

      Out[12]: True


      因為這次調(diào)用get_version的是一個類對象,而不是實例對象,因此object.__getattribute__()會這樣調(diào)用__get__(None, Class)。


      靜態(tài)方法,@staticmethod


      實例和類也可以調(diào)用靜態(tài)方法:


      In [13]: ps.find_version

      Out[13]: __main__.find_version>

       

      In [14]: ps.find_version()

      Out[14]: 0.01

       

      In [15]: vars(ps)

      Out[15]: {'site': 'ghost'}

       

      In [16]: type(ps).__dict__['find_version']

      Out[16]: at 0x108952d70>

       

      In [17]: type(ps).__dict__['find_version'].__get__(ps, type(ps))

      Out[17]: __main__.find_version>

       

      In [18]: type(ps).__dict__['find_version'].__get__(ps, type(ps)) == ps.find_version

      Out[18]: True

       

      In [19]: PythonSite.find_version()

      Out[19]: 0.01

       

      In [20]: PythonSite.find_version

      Out[20]: __main__.find_version>

       

      In [21]: type(ps).__dict__['find_version'].__get__(None, type(ps))

      Out[21]: __main__.find_version>

       

      In [22]: type(ps).__dict__['find_version'].__get__(None, type(ps)) == PythonSite.find_version

      Out[22]: True


      和類方法差別不大,他們的主要差別是在類方法內(nèi)部的時候,類方法可以有cls的類引用,靜態(tài)訪問則沒有,如果靜態(tài)方法想使用類變量,只能硬編碼類名。


      實例方法


      實例方法最為復(fù)雜,是專門屬于實例的,使用類調(diào)用的時候,會是一個unbound方法。


      In [2]: ps.get_site

      Out[2]: method PythonSite.get_site of <__main__.PythonSite object at 0x1054ae2d0>>

       

      In [3]: ps.get_site()

      Out[3]: 'ghost'

       

      In [4]: type(ps).__dict__['get_site']

      Out[4]: __main__.get_site>

       

      In [5]: type(ps).__dict__['get_site'].__get__(ps, type(ps))

      Out[5]: method PythonSite.get_site of <__main__.PythonSite object at 0x1054ae2d0>>

       

      In [6]: type(ps).__dict__['get_site'].__get__(ps, type(ps)) == ps.get_site

      Out[6]: True


      一切工作正常,實例方法也是類的一個屬性,但是對于類,描述器使其變成了unbound方法:


      In [7]: PythonSite.get_site

      Out[7]: method PythonSite.get_site>

       

      In [8]: PythonSite.get_site()

      ---------------------------------------------------------------------------

      TypeError                                 Traceback (most recent call last)

      in ()

      ----> 1 PythonSite.get_site()

       

      TypeError: unbound method get_site() must be called with PythonSite instance as first argument (got nothing instead)

       

      In [9]: PythonSite.get_site(ps)

      Out[9]: 'ghost'

       

      In [10]: PythonSite.__dict__['get_site']

      Out[10]: __main__.get_site>

       

      In [11]: PythonSite.__dict__['get_site'].__get__(None, PythonSite)

      Out[11]: method PythonSite.get_site>

       

      In [12]: PythonSite.__dict__['get_site'].__get__(None, PythonSite) == PythonSite.get_site

      Out[12]: True

       

      In [14]: PythonSite.__dict__['get_site'].__get__(ps, PythonSite)

      Out[14]: method PythonSite.get_site of <__main__.PythonSite object at 0x1054ae2d0>>

       

      In [15]: PythonSite.__dict__['get_site'].__get__(ps, PythonSite)()

      Out[15]: 'ghost'


      由此可見,類不能直接調(diào)用實例方法,除非在描述器手動綁定一個類實例。因為使用類對象調(diào)用描述器的時候,__get__的第一個參數(shù)是None,想要成功調(diào)用,需要把這個參數(shù)替換為實例ps,這個過程就是對方法的bound過程。


      描述器的應(yīng)用


      描述器的作用主要在方法和屬性的定義上。既然我們可以重新描述類的屬性,那么這個魔法就可以改變類的一些行為。最簡單的應(yīng)用則是可以配合裝飾器,寫一個類屬性的緩存。Flask的作者寫了一個werkzeug網(wǎng)絡(luò)工具庫,里面就使用描述器的特性,實現(xiàn)了一個緩存器。


      class _Missing(object):

          def __repr__(self):

              return 'no value'

       

          def __reduce__(self):

              return '_missing'

       

       

      _missing = _Missing()

       

       

      class cached_property(object):

          def __init__(self, func, name=None, doc=None):

              self.__name__ = name or func.__name__

              self.__module__ = func.__module__

              self.__doc__ = doc or func.__doc__

              self.func = func

       

          def __get__(self, obj, type=None):

              if obj is None:

                  return self

              value = obj.__dict__.get(self.__name__, _missing)

              if value is _missing:

                  value = self.func(obj)

                  obj.__dict__[self.__name__] = value

              return value

       

       

      class Foo(object):

          @cached_property

          def foo(self):

              print 'first calculate'

              result = 'this is result'

              return result

       

       

      f = Foo()

       

      print f.foo   # first calculate this is result

      print f.foo   # this is result


      運行結(jié)果可見,first calculate只在第一次調(diào)用時候被計算之后就把結(jié)果緩存起來了。這樣的好處是在網(wǎng)絡(luò)編程中,對HTTP協(xié)議的解析,通常會把HTTP的header解析成python的一個字典,而在視圖函數(shù)的時候,可能不知一次的訪問這個header,因此把這個header使用描述器緩存起來,可以減少多余的解析。


      描述器在python的應(yīng)用十分廣泛,通常是配合裝飾器一起使用。強大的魔法來自強大的責(zé)任。描述器還可以用來實現(xiàn)ORM中對sql語句的”預(yù)編譯”。恰當(dāng)?shù)氖褂妹枋銎鳎梢宰屪约旱腜ython代碼更優(yōu)雅。


        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多