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

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

    • 分享

      深入描述符

       silence_33 2017-08-03

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

      我們先看一個簡單的描述符的例子(基于我之前的分享的Python高級編程改編,這個PPT建議大家去看看):

      1. class MyDescriptor(object):

      2.     _value = ''

      3.     def __get__(self, instance, klass):

      4.         return self._value

      5.     def __set__(self, instance, value):

      6.         self._value = value.swapcase()

      7. class Swap(object):

      8.     swap = MyDescriptor()

      注意MyDescriptor要用新式類。調(diào)用一下:

      1. In [1]: from descriptor_example import Swap

      2. In [2]: instance = Swap()

      3. In [3]: instance.swap  # 沒有報AttributeError錯誤,因為對swap的屬性訪問被描述符類重載了

      4. Out[3]: ''

      5. In [4]: instance.swap = 'make it swap'  # 使用__set__重新設置_value

      6. In [5]: instance.swap

      7. Out[5]: 'MAKE IT SWAP'

      8. In [6]: instance.__dict__  # 沒有用到__dict__:被劫持了

      9. Out[6]: {}

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

      1. >>> class myStaticMethod(object):

      2. ...     def __init__(self, method):

      3. ...         self.staticmethod = method

      4. ...     def __get__(self, object, type=None):

      5. ...         return self.staticmethod

      6. ...

      7. >>> class myClassMethod(object):

      8. ...     def __init__(self, method):

      9. ...         self.classmethod = method

      10. ...     def __get__(self, object, klass=None):

      11. ...         if klass is None:

      12. ...             klass = type(object)

      13. ...         def newfunc(*args):

      14. ...             return self.classmethod(klass, *args)

      15. ...         return newfunc

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

      1. from mongoengine import *                      

      2. class Metadata(EmbeddedDocument):                  

      3.    tags = ListField(StringField())

      4.    revisions = ListField(IntField())

      5. class WikiPage(Document):                          

      6.    title = StringField(required=True)              

      7.    text = StringField()                            

      8.    metadata = EmbeddedDocumentField(Metadata)

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

      1. class BaseField(object):

      2.    name = None

      3.    def __init__(self, **kwargs):

      4.        self.__dict__.update(kwargs)

      5.        ...

      6.    def __get__(self, instance, owner):

      7.        return instance._data.get(self.name)

      8.    def __set__(self, instance, value):

      9.        ...

      10.        instance._data[self.name] = value

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

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

      1. class _Missing(object):

      2.    def __repr__(self):

      3.        return 'no value'

      4.    def __reduce__(self):

      5.        return '_missing'

      6. _missing = _Missing()

      7. class cached_property(property):

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

      9.        self.__name__ = name or func.__name__

      10.        self.__module__ = func.__module__

      11.        self.__doc__ = doc or func.__doc__

      12.        self.func = func

      13.    def __set__(self, obj, value):

      14.        obj.__dict__[self.__name__] = value

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

      16.        if obj is None:

      17.            return self

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

      19.        if value is _missing:

      20.            value = self.func(obj)

      21.            obj.__dict__[self.__name__] = value

      22.        return value

      其實看類的名字就知道這是緩存屬性的,看不懂沒關系,用一下:

      1. class Foo(object):

      2.    @cached_property

      3.    def foo(self):

      4.        print 'Call me!'

      5.        return 42

      調(diào)用下:

      1. In [1]: from cached_property import Foo

      2.   ...: foo = Foo()

      3.   ...:

      4. In [2]: foo.bar

      5. Call me!

      6. Out[2]: 42

      7. In [3]: foo.bar

      8. Out[3]: 42

      可以看到在從第二次調(diào)用bar方法開始,其實用的是緩存的結(jié)果,并沒有真的去執(zhí)行。

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

      1. class Quantity(object):

      2.    def __init__(self, name):

      3.        self.name = name

      4.    def __set__(self, instance, value):

      5.        if value > 0:

      6.            instance.__dict__[self.name] = value

      7.        else:

      8.            raise ValueError('value must be > 0')

      9. class Rectangle(object):

      10.    height = Quantity('height')

      11.    width = Quantity('width')

      12.    def __init__(self, height, width):

      13.        self.height = height

      14.        self.width = width

      15.    @property

      16.    def area(self):

      17.        return self.height * self.width

      我們試一試:

      1. In [1]: from rectangle import Rectangle

      2. In [2]: r = Rectangle(10, 20)

      3. In [3]: r.area

      4. Out[3]: 200

      5. In [4]: r = Rectangle(-1, 20)

      6. ---------------------------------------------------------------------------

      7. ValueError                                Traceback (most recent call last)

      8. ipython-input-5-5a7fc56e8a> in module>()

      9. ----> 1 r = Rectangle(-1, 20)

      10. /Users/dongweiming/mp/2017-03-23/rectangle.py in __init__(self, height, width)

      11.     15

      12.     16     def __init__(self, height, width):

      13. ---> 17         self.height = height

      14.     18         self.width = width

      15.     19

      16. /Users/dongweiming/mp/2017-03-23/rectangle.py in __set__(self, instance, value)

      17.      7             instance.__dict__[self.name] = value

      18.      8         else:

      19. ----> 9             raise ValueError('value must be > 0')

      20.     10

      21.     11

      22. ValueError: value must be > 0

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

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

      1. class Quantity(object):

      2.    __counter = 0

      3.    def __init__(self):

      4.        cls = self.__class__

      5.        prefix = cls.__name__

      6.        index = cls.__counter

      7.        self.name = '_{}#{}'.format(prefix, index)

      8.        cls.__counter += 1

      9.    def __get__(self, instance, owner):

      10.        if instance is None:

      11.            return self

      12.        return getattr(instance, self.name)

      13.    ...

      14. class Rectangle(object):

      15.    height = Quantity()

      16.    width = Quantity()

      17.    ...

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

      1. if instance is None:

      2.    return self

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

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

      1. class Validated(abc.ABC):

      2.    __counter = 0

      3.    def __init__(self):

      4.        cls = self.__class__

      5.        prefix = cls.__name__

      6.        index = cls.__counter

      7.        self.name = '_{}#{}'.format(prefix, index)

      8.        cls.__counter += 1

      9.    def __get__(self, instance, owner):

      10.        if instance is None:

      11.            return self

      12.        else:

      13.            return getattr(instance, self.name)

      14.    def __set__(self, instance, value):

      15.        value = self.validate(instance, value)

      16.        setattr(instance, self.name, value)

      17.    @abc.abstractmethod

      18.    def validate(self, instance, value):

      19.        '''return validated value or raise ValueError'''

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

      1. class Quantity(Validated):

      2.    def validate(self, instance, value):

      3.        if value <> 0:

      4.            raise ValueError('value must be > 0')

      5.        return value

      6. class NonBlank(Validated):

      7.    def validate(self, instance, value):

      8.        value = value.strip()

      9.        if len(value) == 0:

      10.            raise ValueError('value cannot be empty or blank')

      11.        return value

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

      1. def quantity():

      2.    try:

      3.        quantity.counter += 1

      4.    except AttributeError:

      5.        quantity.counter = 0

      6.    storage_name = '_{}:{}'.format('quantity', quantity.counter)

      7.    def qty_getter(instance):

      8.        return getattr(instance, storage_name)

      9.    def qty_setter(instance, value):

      10.        if value > 0:

      11.            setattr(instance, storage_name, value)

      12.        else:

      13.            raise ValueError('value must be > 0')

      14.    return property(qty_getter, qty_setter)

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

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多