91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python中描述符的案例分析

發布時間:2020-08-03 10:25:05 來源:億速云 閱讀:169 作者:清晨 欄目:編程語言

不懂Python中描述符的案例分析?其實想解決這個問題也不難,下面讓小編帶著大家一起學習怎么去解決,希望大家閱讀完這篇文章后大所收獲。

描述符是一種在多個屬性上重復利用同一個存取邏輯的方式,他能"劫持"那些本對于self.__dict__的操作。描述符通常是一種包含__get__、__set__、__delete__三種方法中至少一種的類,給人的感覺是「把一個類的操作托付與另外一個類」。靜態方法、類方法、property都是構建描述符的類。

Python中描述符的案例分析

我們先看一個簡單的描述符的例子:

class MyDescriptor(object):
     _value = ''
     def __get__(self, instance, klass):
         return self._value
     def __set__(self, instance, value):
         self._value = value.swapcase()
class Swap(object):
     swap = MyDescriptor()

注意MyDescriptor要用新式類。調用一下:

In [1]: from descriptor_example import Swap
In [2]: instance = Swap()
In [3]: instance.swap  # 沒有報AttributeError錯誤,因為對swap的屬性訪問被描述符類重載了
Out[3]: ''
In [4]: instance.swap = 'make it swap'  # 使用__set__重新設置_value
In [5]: instance.swap
Out[5]: 'MAKE IT SWAP'
In [6]: instance.__dict__  # 沒有用到__dict__:被劫持了
Out[6]: {}

這就是描述符的威力。我們熟知的staticmethod、classmethod如果你不理解,那么看一下用Python實現的效果可能會更清楚了:

>>> class myStaticMethod(object):
...     def __init__(self, method):
...         self.staticmethod = method
...     def __get__(self, object, type=None):
...         return self.staticmethod
...
>>> class myClassMethod(object):
...     def __init__(self, method):
...         self.classmethod = method
...     def __get__(self, object, klass=None):
...         if klass is None:
...             klass = type(object)
...         def newfunc(*args):
...             return self.classmethod(klass, *args)
...         return newfunc

在實際的生產項目中,描述符有什么用處呢?首先看MongoEngine中的Field的用法:

from mongoengine import *                      
class Metadata(EmbeddedDocument):                   
    tags = ListField(StringField())
    revisions = ListField(IntField())
class WikiPage(Document):                           
    title = StringField(required=True)              
    text = StringField()                            
    metadata = EmbeddedDocumentField(Metadata)

有非常多的Field類型,其實它們的基類就是一個描述符,我簡化下,大家看看實現的原理:

class BaseField(object):
    name = None
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)
        ...
    def __get__(self, instance, owner):
        return instance._data.get(self.name)
    def __set__(self, instance, value):
        ...
        instance._data[self.name] = value

很多項目的源代碼看起來很復雜,在抽絲剝繭之后,其實原理非常簡單,復雜的是業務邏輯。

接著我們再看Flask的依賴Werkzeug中的cached_property:

class _Missing(object):
    def __repr__(self):
        return 'no value'
    def __reduce__(self):
        return '_missing'
_missing = _Missing() 
class cached_property(property):
    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 __set__(self, obj, value):
        obj.__dict__[self.__name__] = value
    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 'Call me!'
        return 42

調用下:

In [1]: from cached_property import Foo
   ...: foo = Foo()
   ...:
In [2]: foo.bar
Call me!
Out[2]: 42
In [3]: foo.bar
Out[3]: 42

可以看到在從第二次調用bar方法開始,其實用的是緩存的結果,并沒有真的去執行。

說了這么多描述符的用法。我們寫一個做字段驗證的描述符:

class Quantity(object):
    def __init__(self, name):
        self.name = name
    def __set__(self, instance, value):
        if value > 0:
            instance.__dict__[self.name] = value
        else:
            raise ValueError('value must be > 0')
class Rectangle(object):
    height = Quantity('height')
    width = Quantity('width')
    def __init__(self, height, width):
        self.height = height
        self.width = width
    @property
    def area(self):
        return self.height * self.width

我們試一試:

In [1]: from rectangle import Rectangle
In [2]: r = Rectangle(10, 20)
In [3]: r.area
Out[3]: 200
In [4]: r = Rectangle(-1, 20)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-5-5a7fc56e8a> in <module>()
----> 1 r = Rectangle(-1, 20)
/Users/dongweiming/mp/2017-03-23/rectangle.py in __init__(self, height, width)
     15
     16     def __init__(self, height, width):
---> 17         self.height = height
     18         self.width = width
     19
/Users/dongweiming/mp/2017-03-23/rectangle.py in __set__(self, instance, value)
      7             instance.__dict__[self.name] = value
      8         else:
----> 9             raise ValueError('value must be > 0')
     10
     11
ValueError: value must be > 0

看到了吧,我們在描述符的類里面對傳值進行了驗證。ORM就是這么玩的!

但是上面的這個實現有個缺點,就是不太自動化,你看height = Quantity('height'),這得讓屬性和Quantity的name都叫做height,那么可不可以不用指定name呢?當然可以,不過實現的要復雜很多:

class Quantity(object):
    __counter = 0
    def __init__(self):
        cls = self.__class__
        prefix = cls.__name__
        index = cls.__counter
        self.name = '_{}#{}'.format(prefix, index)
        cls.__counter += 1
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return getattr(instance, self.name)
    ...
class Rectangle(object):
    height = Quantity()
    width = Quantity() 
    ...

Quantity的name相當于類名+計時器,這個計時器每調用一次就疊加1,用此區分。有一點值得提一提,在__get__中的:

if instance is None:
    return self

在很多地方可見,比如之前提到的MongoEngine中的BaseField。這是由于直接調用Rectangle.height這樣的屬性時候會報AttributeError, 因為描述符是實例上的屬性。

PS:這個靈感來自《Fluent Python》,書中還有一個我認為設計非常好的例子。就是當要驗證的內容種類很多的時候,如何更好地擴展的問題。現在假設我們除了驗證傳入的值要大于0,還得驗證不能為空和必須是數字(當然三種驗證在一個方法中驗證也是可以接受的,我這里就是個演示),我們先寫一個abc的基類:

class Validated(abc.ABC):
    __counter = 0
    def __init__(self):
        cls = self.__class__
        prefix = cls.__name__
        index = cls.__counter
        self.name = '_{}#{}'.format(prefix, index)
        cls.__counter += 1
    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            return getattr(instance, self.name)
    def __set__(self, instance, value):
        value = self.validate(instance, value)
        setattr(instance, self.name, value) 
    @abc.abstractmethod
    def validate(self, instance, value):
        """return validated value or raise ValueError"""

現在新加一個檢查類型,新增一個繼承了Validated的、包含檢查的validate方法的類就可以了:

class Quantity(Validated):
    def validate(self, instance, value):
        if value <= 0:
            raise ValueError('value must be > 0')
        return value
class NonBlank(Validated):
    def validate(self, instance, value):
        value = value.strip()
        if len(value) == 0:
            raise ValueError('value cannot be empty or blank')
        return value

前面展示的描述符都是一個類,那么可不可以用函數來實現呢?也是可以的:

def quantity():
    try:
        quantity.counter += 1
    except AttributeError:
        quantity.counter = 0
    storage_name = '_{}:{}'.format('quantity', quantity.counter)
    def qty_getter(instance):
        return getattr(instance, storage_name)
    def qty_setter(instance, value):
        if value > 0:
            setattr(instance, storage_name, value)
        else:
            raise ValueError('value must be > 0')
    return property(qty_getter, qty_setter)

感謝你能夠認真閱讀完這篇文章,希望小編分享Python中描述符的案例分析內容對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,遇到問題就找億速云,詳細的解決方法等著你來學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

滨州市| 南昌市| 大宁县| 蕲春县| 红安县| 开封县| 仪征市| 双江| 镇雄县| 禹城市| 板桥市| 县级市| 横山县| 长沙县| 呼伦贝尔市| 常德市| 铜川市| 绩溪县| 五指山市| 濮阳市| 玉龙| 成安县| 法库县| 西华县| 赫章县| 靖边县| 谢通门县| 利辛县| 奉贤区| 于都县| 红安县| 新宁县| 仪征市| 阿克苏市| 拜泉县| 乌海市| 宕昌县| 兴义市| 东港市| 平果县| 汝南县|