Python 从attribute到property详解
字面意思上的区别
Attribute与property,都可翻译成属性.虽然无论是在中文中还是英文中它们的意思都几乎一样,但仍有些许差别.Google了好几下,找到了一个看起来比较靠谱的解释:
AccordingtoWebster,apropertyisacharacteristicthatbelongstoathing'sessentialnatureandmaybeusedtodescribeatypeorspecies.
Anattributeisamodifierwordthatservestolimit,identify,particularize,describe,orsupplementthemeaningoftheworditmodifies.
简单来说,property是类的本质属性,可用于定义和描述一个类别或物种;attribute则是用于详细说明它所描述的物体,是物体的具体属性.
例如:人都有嘴巴.有的人嘴巴很大,嘴巴是人的property之一,而大嘴巴只能说是部分人的attribute.
从这个意义上讲,property是attribute的子集.
Python里的attribute与property
回到Python.
Attribute与property在Java中不作区分,但在Python中有所不同.下面是FluentPython(Chapter19)给出的(非正式)定义:
接下来分别解释.
attribute
所有的数据属性(dataattribute)与方法(method)都是attribute.根据attribute的所有者,可分为classattribute与instanceattribute.class或instance的所有attribute都存储在各自的__dict__属性中.
例如:
#Python3 classFoo(): name='Fooclassattribute' deffn(self): pass print('classattribute:',Foo.__dict__) print() foo=Foo() foo.name='fooinstanceattribute' print('instanceattribute:',foo.__dict__)
输出:
classattribute:{'fn':
,...,'name':'Fooclassattribute'} instanceattribute:{'name':'fooinstanceattribute'}
property
property是出于安全考虑用setter/getter方法替代dataattribute,例如,只读属性与属性值合法性验证.
只读属性
例如:
classFoo(): def__init__(self,name): self.name=name foo=Foo('Idonotwanttobechanged') print('foo.name=',foo.name) foo.name='Unluckily,Icanbechanged' print('foo.name=',foo.name)
输出:
foo.name=Idonotwanttobechanged
foo.name=Unluckily,Icanbechanged
在上面的代码中,假如我们只想将foo的name属性暴露给外部读取,但并不想它被修改,我们该怎么办?之前在Python定义只读属性中列出了两种解决方案.第一种方案:”通过私有属性”,其实就是用property替代attribute.
将上面的foo.name改写成property:
classFoo(): def__init__(self,name): self.__name=name @property defname(self): returnself.__name foo=Foo('Idonotwanttobechanged') print('foo.name=',foo.name) foo.name='Luckily,Ireallycannotbechanged'
输出:
foo.name=Idonotwanttobechanged --------------------------------------------------------------------------- AttributeErrorTraceback(mostrecentcalllast)in () 9foo=Foo('Idonotwanttobechanged') 10print('foo.name=',foo.name) --->11foo.name='Luckily,Ireallycannotbechanged' AttributeError:can'tsetattribute
有两点需要注意:
foo.name确实已经不能通过foo.name=...来修改了,即,foo.name已经是只读属性.
将foo.name从attribute变成property之后,它的访问方式并没有改变.也就是说,对外接口没有改变.这个优点可以让我们从容的写代码,不用在一开始就纠结于是使用property还是attribute,因为可以都使用attribute,如果有需要,以后可以在不影响外部代码的前提下随时修改.而在Java里要做到这一点很难(如果可以做到的话).
属性值合法性验证
在上面的例子中,foo.name只有getter方法,是只读的,但其实property也是可修改的,只需要为它添加一个setter方法就行了.那么问题就来了,如果property也是可读可改,那为何要费事将attribute改写成property呢?
想象一个简单的购物相关的业务场景.一个Item代表用户购买的一样东西,主要有类别,价格和数量属性:
classItem(): def__init__(self,category,count,price): self.cat=category self.count=count self.price=price
正常的调用是类似于这样的,价格与数量都是正数:
item=Item('Bread',1,10)
可是,若价格或数量设置为负数也不会报错:
item.price=-10 item.count=-1 invalid_item1=Item('Bread',-1,10) invalid_item2=Item('Bread',1,-10)
从语法上看,这些语句都是合法的,但从业务上看,它们都是不合法的.那么,怎样才能防止这种非法赋值呢?一种解决方案是按照Java风格,实现一个Java式的setter方法,通过item.set_price(price)设置price属性,然后在set_price方法里写验证代码.这样是可行的,但不够Pythonic.Python的风格是读与写都通过属性名进行:
print(item.price)
item.price=-10
这样做的好处之前提到过:将attribute改写成property时不会改变对外接口.那么,如何在执行item.price=-10时检验-10的合法性呢?最直白的方法是在__setattr__方法里设置拦截,但很麻烦,特别是当需要验证的属性很多时.(不信的话可以参照Python定义只读属性的方案二试试).
Python提供的最佳方案是通过property的setter方法:
classItem(): def__init__(self,category,count,price): self.__cat=category#attribute self.count=count#property self.price=price#property @property defcat(self): returnself.__cat @property defcount(self): returnself.__dict__['count'] @count.setter defcount(self,value): ifvalue<0: raiseValueError('countcannotbeminus:%r'%(value)) self.__dict__['count']=value @property defprice(self): returnself.__dict__['price'] @price.setter defprice(self,value): ifvalue<0: raiseValueError('pricecannotbeminus:%r'%(value)) self.__dict__['price']=value
之前合法的语句现在仍然可以正常运行:
item=Item('Bread',1,10) item.price=20 item.count=2 print(item.price)
但下面的语句执行时便会报错了:
item=Item('Bread',1,-10) #or item.price=-10
会报出同一个错误:
--------------------------------------------------------------------------- ValueErrorTraceback(mostrecentcalllast)in () ---->1item.price=-10 inprice(self,value) 27defprice(self,value): 28ifvalue<0: --->29raiseValueError('pricecannotbeminus:%r'%(value)) 30self.__dict__['price']=value ValueError:pricecannotbeminus:-10
定义property的其他方式
@property中的property虽可被当作修饰器来使用,但它其实是一个class(具体API请参考文档),所以上面的代码还可以改写为:
classItem(): def__init__(self,category,count,price): self.__cat=category#attribute self.count=count#property self.price=price#property defget_cat(self): returnself.__cat defget_count(self): returnself.__dict__['count'] defset_count(self,value): ifvalue<0: raiseValueError('countcannotbeminus:%r'%(value)) self.__dict__['count']=value defget_price(self): returnself.__dict__['price'] defset_price(self,value): ifvalue<0: raiseValueError('pricecannotbeminus:%r'%(value)) self.__dict__['price']=value bill=property(get_bill) cat=property(get_cat) count=property(get_count,set_count) price=property(get_price,set_price)
功能上达到要求了,可代码本身看起来很冗长,比Java中的getter/setter风格还要长.这时可以通过propertyfactory来简化代码:
先定义可共用的propertyfactory函数:
defreadonly_prop(storage_name): defgetter(instance): returninstance.__dict__[storage_name] returnproperty(getter) defpositive_mutable_prop(storage_name): defgetter(instance): returninstance.__dict__[storage_name] defsetter(instance,value): ifvalue<0: raiseValueError('%scannotbeminus:%r'%(storage_name,value)) instance.__dict__[storage_name]=value returnproperty(getter,setter)
然后,之前的示例代码可以简化为:
classItem(): def__init__(self,category,count,price): self.__cat=category#attribute self.count=count#property self.price=price#property cat=readonly_prop('__cat') count=positive_mutable_prop('count') price=positive_mutable_prop('price')
这样一来,在保证代码简洁的前提下实现了访问控制和合法性验证.
property不会被instanceattribute覆盖
之前在Python对象的属性访问过程一文中展示了attribute的解析过程,从中知道classattribute可以被instanceattribute覆盖:
classFoo(): name='Foo' foo=Foo() foo.name='foo' codes=['Foo.name','foo.name'] forcodeincodes: print(code,'=',eval(code))
输出为:
Foo.name=Foo
foo.name=foo
但在property身上不会发生这种事情:
classFoo(): @property defname(self): return'Foo' foo=Foo() foo.__dict__['name']='foo'#已经不能通过foo.name赋值了 codes=['Foo.name','foo.name'] forcodeincodes: print(code,'=',eval(code))
输出:
Foo.name=
foo.name=Foo
至少可以看出两点:
1.通过classFoo访问Foo.name得到的是property对象,而非property值.
2.访问foo.name时返回的是Foo.name的property值.究其原因,是因为在属性解析过程中,property的优先级是最高的.
总结
1.Python的attribute与property不同:
attribute:dataattribute+method
property:replaceattributewithaccesscontrolmethodslikegetter/setter,forsecurityreasons.
2.可以通过多种方式定义property:
@property
property(getter,setter)
propertyfactory
3.property在属性解析时的优先级最高,不会被instanceattribute覆盖.
以上这篇Python从attribute到property详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。