Python 从attribute到property详解


Posted in Python onMarch 05, 2020

字面意思上的区别

Attribute与property, 都可翻译成属性. 虽然无论是在中文中还是英文中 它们的意思都几乎一样, 但仍有些许差别. Google了好几下, 找到了一个看起来比较靠谱的解释:

According to Webster, a property is a characteristic that belongs to a thing's essential nature and may be used to describe a type or species.

An attribute is a modifier word that serves to limit, identify, particularize, describe, or supplement the meaning of the word it modifies.

简单来说, property是类的本质属性, 可用于定义和描述一个类别或物种; attribute则是用于详细说明它所描述的物体, 是物体的具体属性.

例如: 人都有嘴巴. 有的人嘴巴很大, 嘴巴是人的property之一, 而大嘴巴只能说是部分人的attribute.

从这个意义上讲, property是attribute的子集.

Python里的attribute与property

回到Python.

Attribute与property在Java中不作区分, 但在Python中有所不同. 下面是Fluent Python(Chapter 19)给出的(非正式)定义:

Python 从attribute到property详解

接下来分别解释.

attribute

所有的数据属性(data attribute)与方法(method)都是attribute. 根据attribute的所有者, 可分为class attribute与instance attribute. class或instance的所有attribute都存储在各自的__dict__属性中.

例如:

# Python3
class Foo():
 name = 'Foo class attribute'
 def fn(self):
  pass
print('class attribute:', Foo.__dict__)
print()
foo = Foo()
foo.name = 'foo instance attribute'
print('instance attribute:', foo.__dict__)

输出:

class attribute: {'fn': <function Foo.fn at 0x7fd135ec8ea0>, ... , 'name': 'Foo class attribute'}

instance attribute: {'name': 'foo instance attribute'}

property

property是出于安全考虑用setter/getter方法替代data attribute, 例如, 只读属性与属性值合法性验证.

只读属性

例如:

class Foo():
 def __init__(self, name):
  self.name = name

foo = Foo('I do not want to be changed')
print('foo.name = ', foo.name)
foo.name = 'Unluckily, I can be changed'
print('foo.name = ', foo.name)

输出:

foo.name = I do not want to be changed
foo.name = Unluckily, I can be changed

在上面的代码中, 假如我们只想将foo的name属性暴露给外部读取, 但并不想它被修改, 我们该怎么办? 之前在Python 定义只读属性中列出了两种解决方案. 第一种方案:”通过私有属性”, 其实就是用property替代attribute.

将上面的foo.name改写成property:

class Foo():
 def __init__(self, name):
  self.__name = name

 @property
 def name(self):
  return self.__name

foo = Foo('I do not want to be changed')
print('foo.name = ', foo.name)
foo.name = 'Luckily, I really can not be changed'

输出:

foo.name = I do not want to be changed

---------------------------------------------------------------------------
AttributeError       Traceback (most recent call last)
<ipython-input-69-101c96ba497e> in <module>()
  9 foo = Foo('I do not want to be changed')
  10 print('foo.name = ', foo.name)
---> 11 foo.name = 'Luckily, I really can not be changed'

AttributeError: can't set attribute

有两点需要注意:

foo.name确实已经不能通过foo.name = ...来修改了, 即, foo.name已经是只读属性.

将foo.name从attribute变成property之后, 它的访问方式并没有改变. 也就是说, 对外接口没有改变. 这个优点可以让我们从容的写代码, 不用在一开始就纠结于是使用property还是attribute, 因为可以都使用attribute, 如果有需要, 以后可以在不影响外部代码的前提下随时修改. 而在Java里要做到这一点很难(如果可以做到的话).

属性值合法性验证

在上面的例子中, foo.name只有getter方法, 是只读的, 但其实property也是可修改的, 只需要为它添加一个setter方法就行了. 那么问题就来了, 如果property也是可读可改, 那为何要费事将attribute改写成property呢?

想象一个简单的购物相关的业务场景. 一个Item代表用户购买的一样东西, 主要有类别, 价格和数量属性:

class Item():
 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方法:

class Item():
 def __init__(self, category, count, price):
  self.__cat = category # attribute
  self.count = count # property
  self.price = price # property

 @property
 def cat(self):
  return self.__cat

 @property
 def count(self):
  return self.__dict__['count']
 @count.setter
 def count(self, value):
  if value < 0:
   raise ValueError('count can not be minus: %r'%(value))
  self.__dict__['count'] = value

 @property
 def price(self):
  return self.__dict__['price']

 @price.setter
 def price(self, value):
  if value < 0:
   raise ValueError('price can not be minus: %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

会报出同一个错误:

---------------------------------------------------------------------------
ValueError        Traceback (most recent call last)
<ipython-input-93-4fcbd1284b2d> in <module>()
----> 1 item.price = -10

<ipython-input-91-7546240b5469> in price(self, value)
  27  def price(self, value):
  28   if value < 0:
---> 29    raise ValueError('price can not be minus: %r'%(value))
  30   self.__dict__['price'] = value

ValueError: price can not be minus: -10

定义property的其他方式

@property中的property虽可被当作修饰器来使用, 但它其实是一个class(具体API请参考文档), 所以上面的代码还可以改写为:

class Item():
 def __init__(self, category, count, price):
  self.__cat = category # attribute
  self.count = count # property
  self.price = price # property

 def get_cat(self):
  return self.__cat

 def get_count(self):
  return self.__dict__['count']

 def set_count(self, value):
  if value < 0:
   raise ValueError('count can not be minus: %r'%(value))
  self.__dict__['count'] = value

 def get_price(self):
  return self.__dict__['price']

 def set_price(self, value):
  if value < 0:
   raise ValueError('price can not be minus: %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风格还要长. 这时可以通过property factory来简化代码:

先定义可共用的property factory函数:

def readonly_prop(storage_name):
 def getter(instance):
  return instance.__dict__[storage_name]
 return property(getter)
def positive_mutable_prop(storage_name):
 def getter(instance):
  return instance.__dict__[storage_name]
 def setter(instance, value):
  if value < 0:
   raise ValueError('%s can not be minus: %r'%(storage_name, value))
  instance.__dict__[storage_name] = value
 return property(getter, setter)

然后, 之前的示例代码可以简化为:

class Item():
 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不会被instance attribute覆盖

之前在Python对象的属性访问过程一文中展示了attribute的解析过程, 从中知道class attribute可以被instance attribute覆盖:

class Foo():
 name = 'Foo'

foo = Foo()
foo.name = 'foo'
codes = ['Foo.name', 'foo.name']
for code in codes:
 print(code, '=', eval(code))

输出为:

Foo.name = Foo
foo.name = foo

但在property身上不会发生这种事情:

class Foo():
 @property
 def name(self):
  return 'Foo'

foo = Foo()
foo.__dict__['name'] = 'foo'# 已经不能通过foo.name赋值了
codes = ['Foo.name', 'foo.name']
for code in codes:
 print(code, '=', eval(code))

输出:

Foo.name = <property object at 0x7fd135e7ecc8>
foo.name = Foo

至少可以看出两点:

1. 通过class Foo访问Foo.name得到的是property对象, 而非property值.

2. 访问 foo.name时返回的是Foo.name的property值. 究其原因, 是因为在属性解析过程中, property的优先级是最高的.

总结

1.Python的attribute与property不同:

attribute: data attribute + method

property: replace attribute with access control methods like getter/setter, for security reasons.

2.可以通过多种方式定义property:

@property

property(getter, setter)

property factory

3.property在属性解析时的优先级最高, 不会被instance attribute覆盖.

以上这篇Python 从attribute到property详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python实现读取并保存文件的类
May 11 Python
Python 含参构造函数实例详解
May 25 Python
django2+uwsgi+nginx上线部署到服务器Ubuntu16.04
Jun 26 Python
Python3爬虫学习之爬虫利器Beautiful Soup用法分析
Dec 12 Python
VPS CENTOS 上配置python,mysql,nginx,uwsgi,django的方法详解
Jul 01 Python
Python logging设置和logger解析
Aug 28 Python
Python进度条的制作代码实例
Aug 31 Python
FFT快速傅里叶变换的python实现过程解析
Oct 21 Python
在python中求分布函数相关的包实例
Apr 15 Python
Python Tricks 使用 pywinrm 远程控制 Windows 主机的方法
Jul 21 Python
Python rabbitMQ如何实现生产消费者模式
Aug 24 Python
Pycharm配置lua编译环境过程图解
Nov 28 Python
Python+OpenCV实现图像的全景拼接
Mar 05 #Python
Python对象的属性访问过程详解
Mar 05 #Python
Python安装OpenCV的示例代码
Mar 05 #Python
opencv python在视屏上截图功能的实现
Mar 05 #Python
谈谈Python:为什么类中的私有属性可以在外部赋值并访问
Mar 05 #Python
python如何将两张图片生成为全景图片
Mar 05 #Python
Python 定义只读属性的实现方式
Mar 05 #Python
You might like
DOTA2 6.87版本后新眼位详解攻略
2020/04/20 DOTA
PHP函数常用用法小结
2010/02/08 PHP
PHP中break及continue两个流程控制指令区别分析
2011/04/18 PHP
php+ajax实现带进度条的上传图片功能【附demo源码下载】
2016/09/14 PHP
js模拟实现Array的sort方法
2007/12/11 Javascript
Jquery 常用方法经典总结
2010/01/28 Javascript
多个表单中如何获得这个文件上传的网址实现js代码
2013/03/25 Javascript
JS比较两个时间大小的简单示例代码
2013/12/20 Javascript
无刷新预览所选择的图片示例代码
2014/04/02 Javascript
前端轻量级MVC框架CanJS详解
2014/09/26 Javascript
Javascript 拖拽的一些高级的应用(逐行分析代码,让你轻松了拖拽的原理)
2015/01/23 Javascript
JS实现的页面自定义滚动条效果
2015/10/26 Javascript
JavaScript获取浏览器信息的方法
2015/11/20 Javascript
js表单中选择框值的获取及表单的序列化
2015/12/17 Javascript
AngularJS初始化静态模板详解
2016/01/14 Javascript
jQuery qrcode生成二维码的方法
2016/04/03 Javascript
js style.display=block显示布局错乱问题的解决方法
2016/09/21 Javascript
html5 canvas 详细使用教程
2017/01/20 Javascript
Angular X中使用ngrx的方法详解(附源码)
2017/07/10 Javascript
Vue.js中组件中的slot实例详解
2017/07/17 Javascript
react 国际化的实现代码示例
2018/09/14 Javascript
es6中使用map简化复杂条件判断操作实例详解
2020/02/19 Javascript
Node 使用express-http-proxy 做api网关的实现
2020/10/15 Javascript
[10:05]DOTA2-DPC中国联赛 正赛 iG vs PSG.LGD 选手采访
2021/03/11 DOTA
Python判断两个list是否是父子集关系的实例
2018/05/04 Python
python爬取个性签名的方法
2018/06/17 Python
对python中矩阵相加函数sum()的使用详解
2019/01/28 Python
python根据文章标题内容自动生成摘要的实例
2019/02/21 Python
python支付宝支付示例详解
2019/08/22 Python
Python根据服务获取端口号的方法
2019/09/25 Python
前端隐藏出边界内容的实现方法
2016/04/14 HTML / CSS
印度网上购物首选目的地:Flipkart
2016/08/01 全球购物
公务员个人自我评价分享
2013/11/06 职场文书
小学教师师德演讲稿
2014/05/06 职场文书
小学生安全责任书
2014/07/25 职场文书
民事申诉状范本
2015/05/20 职场文书