小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

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

 River_LaLaLa 2016-08-21


來源:人世間

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


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


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


何謂描述器


當定義迭代器的時候,描述是實現(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類屬性,并且是同一個類屬性。當pysite1修改了version,實際上是給自己添加了一個version屬性。類屬性并沒有被改變。當PythonSite改變了version屬性的時候,pysite2的該屬性也對應被改變。


屬性訪問的原理與描述器


知道了屬性訪問的結(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)方法想使用類變量,只能硬編碼類名。


實例方法


實例方法最為復雜,是專門屬于實例的,使用類調(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過程。


描述器的應用


描述器的作用主要在方法和屬性的定義上。既然我們可以重新描述類的屬性,那么這個魔法就可以改變類的一些行為。最簡單的應用則是可以配合裝飾器,寫一個類屬性的緩存。Flask的作者寫了一個werkzeug網(wǎng)絡工具庫,里面就使用描述器的特性,實現(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)絡編程中,對HTTP協(xié)議的解析,通常會把HTTP的header解析成python的一個字典,而在視圖函數(shù)的時候,可能不知一次的訪問這個header,因此把這個header使用描述器緩存起來,可以減少多余的解析。


描述器在python的應用十分廣泛,通常是配合裝飾器一起使用。強大的魔法來自強大的責任。描述器還可以用來實現(xiàn)ORM中對sql語句的”預編譯”。恰當?shù)氖褂妹枋銎?,可以讓自己的Python代碼更優(yōu)雅。


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

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多