Python描述符,python文件描述符
描述符是一种在多个属性上重用相同访问逻辑的方法,它可以“劫持”那些原本属于自己的操作。__词典_ _。描述符通常是至少包含__get__ 、__set__、和__delete__方法之一的类,给人一种“将一个类的操作委托给另一个类”的感觉。静态方法、类方法和属性都是构建描述符的类。
让我们首先看一个简单描述符的例子:
classMyDescriptor(对象):
_value=
def__get__(self,instance,klass):
回归自我。_值
def__set__(自身,实例,值):
自我。_value=value.swapcase()
classSwap(object):
Swap=MyDescriptor()注意,MyDescriptor应该使用一个新类。打电话:
in[1]: from descriptor _ exampleimportSwap
In[2]:instance=Swap()
In[3]:instance.swap#未报告AttributeError错误,因为对swap的属性访问被descriptor类重载。
Out[3]:
in[4]: instance . swap= make it WAP # use _ _ set _ _重置_value。
In[5]:instance.swap
Out[5]:MAKEITSWAP
在[6]:实例中。__dict__#未使用_ _ dict _ _ 3360被劫持
Out[6]:{}这就是描述符的力量。如果你不理解众所周知的staticmethod和classmethod,看一下用Python实现的效果可能会更清楚:
classmyStaticMethod(object):
.def__init__(self,method):
.self.staticmethod=方法
.def__get__(self,object,type=None):
.returnself.staticmethod
.
classmyClassMethod(对象):
.def__init__(self,method):
.self.classmethod=方法
.def__get__(self,object,klass=None):
.
nbsp;ifklassisNone:
...klass=type(object)
...defnewfunc(*args):
...returnself.classmethod(klass,*args)
...returnnewfunc在实际的生产项目中,描述符有什么用处呢?首先看MongoEngine中的Field的用法:
frommongoengineimport*有非常多的Field类型,其实它们的基类就是一个描述符,我简化下,大家看看实现的原理:classMetadata(EmbeddedDocument):
tags=ListField(StringField())
revisions=ListField(IntField())
classWikiPage(Document):
title=StringField(required=True)
text=StringField()
metadata=EmbeddedDocumentField(Metadata)
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):
return'novalue'
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):
print'Callme!'
return42
In[1]:fromcached_propertyimportFoo可以看到在从第二次调用bar方法开始,其实用的是缓存的结果,并没有真的去执行。...:foo=Foo()
...:
In[2]:foo.bar
Callme!
Out[2]:42
In[3]:foo.bar
Out[3]:42
说了这么多描述符的用法。我们写一个做字段验证的描述符:
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看到了吧,我们在描述符的类里面对传值进行了验证。ORM就是这么玩的!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
但是上面的这个实现有个缺点,就是不太自动化,你看height = Quantity('height'),这得让属性和Quantity的name都叫做height,那么可不可以不用指定name呢?当然可以,不过实现的要复杂很多:
classQuantity(object):Quantity的name相当于类名+计时器,这个计时器每调用一次就叠加1,用此区分。有一点值得提一提,在__get__中的:__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()
...
ifinstanceisNone:在很多地方可见,比如之前提到的MongoEngine中的BaseField。这是由于直接调用Rectangle.height这样的属性时候会报AttributeError, 因为描述符是实例上的属性。returnself
PS:这个灵感来自《Fluent Python》,书中还有一个我认为设计非常好的例子。就是当要验证的内容种类很多的时候,如何更好地扩展的问题。现在假设我们除了验证传入的值要大于0,还得验证不能为空和必须是数字(当然三种验证在一个方法中验证也是可以接受的,我这里就是个演示),我们先写一个abc的基类:
classValidated(abc.ABC):现在新加一个检查类型,新增一个继承了Validated的、包含检查的validate方法的类就可以了:__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"""
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)
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。