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

成都創(chuàng)新互聯(lián)公司專業(yè)為企業(yè)提供靜樂(lè)網(wǎng)站建設(shè)、靜樂(lè)做網(wǎng)站、靜樂(lè)網(wǎng)站設(shè)計(jì)、靜樂(lè)網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)與制作、靜樂(lè)企業(yè)網(wǎng)站模板建站服務(wù),十載靜樂(lè)做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
我們先看一個(gè)簡(jiǎn)單的描述符的例子:
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要用新式類。調(diào)用一下:
In [1]: from descriptor_example import Swap
In [2]: instance = Swap()
In [3]: instance.swap # 沒(méi)有報(bào)AttributeError錯(cuò)誤,因?yàn)閷?duì)swap的屬性訪問(wèn)被描述符類重載了
Out[3]: ''
In [4]: instance.swap = 'make it swap' # 使用__set__重新設(shè)置_value
In [5]: instance.swap
Out[5]: 'MAKE IT SWAP'
In [6]: instance.__dict__ # 沒(méi)有用到__dict__:被劫持了
Out[6]: {}這就是描述符的威力。我們熟知的staticmethod、classmethod如果你不理解,那么看一下用python實(shí)現(xiàn)的效果可能會(huì)更清楚了:
>>> 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
在實(shí)際的生產(chǎn)項(xiàng)目中,描述符有什么用處呢?首先看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類型,其實(shí)它們的基類就是一個(gè)描述符,我簡(jiǎn)化下,大家看看實(shí)現(xiàn)的原理:
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
很多項(xiàng)目的源代碼看起來(lái)很復(fù)雜,在抽絲剝繭之后,其實(shí)原理非常簡(jiǎn)單,復(fù)雜的是業(yè)務(wù)邏輯。
接著我們?cè)倏碏lask的依賴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
其實(shí)看類的名字就知道這是緩存屬性的,看不懂沒(méi)關(guān)系,用一下:
class Foo(object): @cached_property def foo(self): print 'Call me!' return 42
調(diào)用下:
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
可以看到在從第二次調(diào)用bar方法開(kāi)始,其實(shí)用的是緩存的結(jié)果,并沒(méi)有真的去執(zhí)行。
說(shuō)了這么多描述符的用法。我們寫(xiě)一個(gè)做字段驗(yàn)證的描述符:
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我們?cè)囈辉嚕?/p>
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)in () ----> 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
看到了吧,我們?cè)诿枋龇念惱锩鎸?duì)傳值進(jìn)行了驗(yàn)證。ORM就是這么玩的!
但是上面的這個(gè)實(shí)現(xiàn)有個(gè)缺點(diǎn),就是不太自動(dòng)化,你看height = Quantity('height'),這得讓屬性和Quantity的name都叫做height,那么可不可以不用指定name呢?當(dāng)然可以,不過(guò)實(shí)現(xiàn)的要復(fù)雜很多:
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相當(dāng)于類名+計(jì)時(shí)器,這個(gè)計(jì)時(shí)器每調(diào)用一次就疊加1,用此區(qū)分。有一點(diǎn)值得提一提,在__get__中的:
if instance is None: return self
在很多地方可見(jiàn),比如之前提到的MongoEngine中的BaseField。這是由于直接調(diào)用Rectangle.height這樣的屬性時(shí)候會(huì)報(bào)AttributeError, 因?yàn)槊枋龇菍?shí)例上的屬性。
PS:這個(gè)靈感來(lái)自《Fluent Python》,書(shū)中還有一個(gè)我認(rèn)為設(shè)計(jì)非常好的例子。就是當(dāng)要驗(yàn)證的內(nèi)容種類很多的時(shí)候,如何更好地?cái)U(kuò)展的問(wèn)題?,F(xiàn)在假設(shè)我們除了驗(yàn)證傳入的值要大于0,還得驗(yàn)證不能為空和必須是數(shù)字(當(dāng)然三種驗(yàn)證在一個(gè)方法中驗(yàn)證也是可以接受的,我這里就是個(gè)演示),我們先寫(xiě)一個(gè)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"""現(xiàn)在新加一個(gè)檢查類型,新增一個(gè)繼承了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前面展示的描述符都是一個(gè)類,那么可不可以用函數(shù)來(lái)實(shí)現(xiàn)呢?也是可以的:
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) 分享標(biāo)題:創(chuàng)新互聯(lián)Python教程:Python中的描述符
文章源于:http://fisionsoft.com.cn/article/ccsehcs.html


咨詢
建站咨詢
