首页IT科技python函数描述(Python中的描述符)

python函数描述(Python中的描述符)

时间2025-05-04 17:12:22分类IT科技浏览3660
导读:描述符是一种在多个属性上重复利用同一个存取逻辑的方式,他能"劫持"那些本对于self.__dict__的操作。描述符通常是一种包含__get__、__set__、__delete__三种方法中至少一种的类,给人的感觉是「把一个类的操作托付与另外一个类」。静态方法、类方法、property都是构建描述符的类。...

描述符是一种在多个属性上重复利用同一个存取逻辑的方式            ,他能"劫持"那些本对于self.__dict__的操作            。描述符通常是一种包含__get__           、__set__                  、__delete__三种方法中至少一种的类                 ,给人的感觉是「把一个类的操作托付与另外一个类」                 。静态方法      、类方法        、property都是构建描述符的类      。

我们先看一个简单的描述符的例子:

classMyDescriptor(object): _value= def__get__(self,instance,klass): returnself._value def__set__(self,instance,value): self._value=value.swapcase() classSwap(object): swap=MyDescriptor()

注意MyDescriptor要用新式类         。调用一下:

In[1]:fromdescriptor_exampleimportSwap In[2]:instance=Swap() In[3]:instance.swap#没有报AttributeError错误      ,因为对swap的属性访问被描述符类重载了 Out[3]: In[4]:instance.swap=makeitswap#使用__set__重新设置_value In[5]:instance.swap Out[5]:MAKEITSWAP In[6]:instance.__dict__#没有用到__dict__:被劫持了 Out[6]:{}

这就是描述符的威力                 。我们熟知的staticmethod                  、classmethod如果你不理解         ,那么看一下用Python实现的效果可能会更清楚了:

>>>classmyStaticMethod(object): ...def__init__(self,method): ...self.staticmethod=method ...def__get__(self,object,type=None): ...returnself.staticmethod ... >>>classmyClassMethod(object): ...def__init__(self,method): ...self.classmethod=method ...def__get__(self,object,klass=None): ...ifklassisNone: ...klass=type(object) ...defnewfunc(*args): ...returnself.classmethod(klass,*args) ...returnnewfunc

在实际的生产项目中                 ,描述符有什么用处呢?首先看MongoEngine中的Field的用法:

frommongoengineimport* classMetadata(EmbeddedDocument): tags=ListField(StringField()) revisions=ListField(IntField()) classWikiPage(Document): title=StringField(required=True) text=StringField() metadata=EmbeddedDocumentField(Metadata)

有非常多的Field类型         ,其实它们的基类就是一个描述符      ,我简化下                 ,大家看看实现的原理:

classBaseField(object): name=None def__init__(self,**kwargs): self.__dict__.update(kwargs) ... def__get__(self,instance,owner): returninstance._data.get(self.name) def__set__(self,instance,value): ... instance._data[self.name]=value

很多项目的源代码看起来很复杂           ,在抽丝剥茧之后   ,其实原理非常简单                  ,复杂的是业务逻辑         。

接着我们再看Flask的依赖Werkzeug中的cached_property:

class_Missing(object): def__repr__(self): returnnovalue def__reduce__(self): return_missing _missing=_Missing() classcached_property(property): def__init__(self,func,name=None,doc=None): self.__name__=nameorfunc.__name__ self.__module__=func.__module__ self.__doc__=docorfunc.__doc__ self.func=func def__set__(self,obj,value): obj.__dict__[self.__name__]=value def__get__(self,obj,type=None): ifobjisNone: returnself value=obj.__dict__.get(self.__name__,_missing) ifvalueis_missing: value=self.func(obj) obj.__dict__[self.__name__]=value returnvalue

其实看类的名字就知道这是缓存属性的              ,看不懂没关系,用一下:

classFoo(object): @cached_property deffoo(self): printCallme! return42

调用下:

In[1]:fromcached_propertyimportFoo ...:foo=Foo() ...: In[2]:foo.bar Callme! Out[2]:42 In[3]:foo.bar Out[3]:42

可以看到在从第二次调用bar方法开始               ,其实用的是缓存的结果                 ,并没有真的去执行      。

说了这么多描述符的用法                 。我们写一个做字段验证的描述符:

classQuantity(object): def__init__(self,name): self.name=name def__set__(self,instance,value): ifvalue>0: instance.__dict__[self.name]=value else: raiseValueError(valuemustbe>0) classRectangle(object): height=Quantity(height) width=Quantity(width) def__init__(self,height,width): self.height=height self.width=width @property defarea(self): returnself.height*self.width

我们试一试:

In[1]:fromrectangleimportRectangle In[2]:r=Rectangle(10,20) In[3]:r.area Out[3]:200 In[4]:r=Rectangle(-1,20) --------------------------------------------------------------------------- ValueErrorTraceback(mostrecentcalllast) <ipython-input-5-5a7fc56e8a>in<module>() ---->1r=Rectangle(-1,20) /Users/dongweiming/mp/2017-03-23/rectangle.pyin__init__(self,height,width) 15 16def__init__(self,height,width): --->17self.height=height 18self.width=width 19 /Users/dongweiming/mp/2017-03-23/rectangle.pyin__set__(self,instance,value) 7instance.__dict__[self.name]=value 8else: ---->9raiseValueError(valuemustbe>0) 10 11 ValueError:valuemustbe>0

看到了吧   ,我们在描述符的类里面对传值进行了验证           。ORM就是这么玩的!

但是上面的这个实现有个缺点            ,就是不太自动化                 ,你看height = Quantity(height)      ,这得让属性和Quantity的name都叫做height         ,那么可不可以不用指定name呢?当然可以                 ,不过实现的要复杂很多:

classQuantity(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): ifinstanceisNone: returnself returngetattr(instance,self.name) ... classRectangle(object): height=Quantity() width=Quantity() ...

Quantity的name相当于类名+计时器         ,这个计时器每调用一次就叠加1      ,用此区分   。有一点值得提一提                 ,在__get__中的:

ifinstanceisNone: returnself

在很多地方可见           ,比如之前提到的MongoEngine中的BaseField                  。这是由于直接调用Rectangle.height这样的属性时候会报AttributeError, 因为描述符是实例上的属性              。

PS:这个灵感来自《Fluent Python》   ,书中还有一个我认为设计非常好的例子。就是当要验证的内容种类很多的时候                  ,如何更好地扩展的问题               。现在假设我们除了验证传入的值要大于0              ,还得验证不能为空和必须是数字(当然三种验证在一个方法中验证也是可以接受的,我这里就是个演示)               ,我们先写一个abc的基类:

classValidated(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): ifinstanceisNone: returnself else: returngetattr(instance,self.name) def__set__(self,instance,value): value=self.validate(instance,value) setattr(instance,self.name,value) @abc.abstractmethod defvalidate(self,instance,value): """returnvalidatedvalueorraiseValueError"""

现在新加一个检查类型                 ,新增一个继承了Validated的         、包含检查的validate方法的类就可以了:

classQuantity(Validated): defvalidate(self,instance,value): ifvalue<=0: raiseValueError(valuemustbe>0) returnvalue classNonBlank(Validated): defvalidate(self,instance,value): value=value.strip() iflen(value)==0: raiseValueError(valuecannotbeemptyorblank) returnvalue defquantity(): try: quantity.counter+=1 exceptAttributeError: quantity.counter=0 storage_name=_{}:{}.format(quantity,quantity.counter) defqty_getter(instance): returngetattr(instance,storage_name) defqty_setter(instance,value): ifvalue>0: setattr(instance,storage_name,value) else: raiseValueError(valuemustbe>0) returnproperty(qty_getter,qty_setter)

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
爬虫数据采集(探析爬虫工具:解读其意义与功能) seo就业如何(SEO就业前景伪原创怎么写)