您好,登錄后才能下訂單哦!
不懂Python中描述符的案例分析?其實想解決這個問題也不難,下面讓小編帶著大家一起學習怎么去解決,希望大家閱讀完這篇文章后大所收獲。
描述符是一種在多個屬性上重復利用同一個存取邏輯的方式,他能"劫持"那些本對于self.__dict__的操作。描述符通常是一種包含__get__、__set__、__delete__三種方法中至少一種的類,給人的感覺是「把一個類的操作托付與另外一個類」。靜態方法、類方法、property都是構建描述符的類。
我們先看一個簡單的描述符的例子:
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中描述符的案例分析內容對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,遇到問題就找億速云,詳細的解決方法等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。