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 相关文章推荐
解决安装tensorflow遇到无法卸载numpy 1.8.0rc1的问题
Jun 13 Python
详解Python 调用C# dll库最简方法
Jun 20 Python
树莓派用python中的OpenCV输出USB摄像头画面
Jun 22 Python
pandas 使用均值填充缺失值列的小技巧分享
Jul 04 Python
python Gunicorn服务器使用方法详解
Jul 22 Python
ansible动态Inventory主机清单配置遇到的坑
Jan 19 Python
Tensorflow累加的实现案例
Feb 05 Python
Python面向对象程序设计之静态方法、类方法、属性方法原理与用法分析
Mar 23 Python
python接入支付宝的实例操作
Jul 20 Python
Python通过类的组合模拟街道红绿灯
Sep 16 Python
python中把元组转换为namedtuple方法
Dec 09 Python
Python读取pdf表格写入excel的方法
Jan 22 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
编写PHP的安全策略
2006/10/09 PHP
自己写的php中文截取函数mb_strlen和mb_substr
2015/02/09 PHP
10个对初学者非常有用的PHP技巧
2016/04/06 PHP
PHP编程求最大公约数与最小公倍数的方法示例
2017/05/29 PHP
PHP实现的简单在线计算器功能示例
2017/08/02 PHP
php生成word并下载代码实例
2019/03/15 PHP
Laravel相关的一些故障解决
2020/08/19 PHP
jQuery 浮动广告实现代码
2008/12/25 Javascript
JQuery 网站换肤功能实现代码
2009/11/02 Javascript
分享一个我自己写的ToolTip提示插件(附源码)
2013/01/20 Javascript
javascript实现浏览器窗口传递参数的方法
2014/09/03 Javascript
JavaScript中的replace()方法使用详解
2015/06/06 Javascript
jQuery使用serialize()表单序列化时出现中文乱码问题的解决办法
2016/07/27 Javascript
jQuery使用getJSON方法获取json数据完整示例
2016/09/13 Javascript
node+experss实现爬取电影天堂爬虫
2016/11/20 Javascript
浅谈Node.js 中间件模式
2018/06/12 Javascript
JavaScript arguments.callee作用及替换方案详解
2020/09/02 Javascript
JavaScript 声明私有变量的两种方式
2021/02/05 Javascript
用Python解析XML的几种常见方法的介绍
2015/04/09 Python
Python中的getopt函数使用详解
2015/07/28 Python
win8下python3.4安装和环境配置图文教程
2018/07/31 Python
django框架基于模板 生成 excel(xls) 文件操作示例
2019/06/19 Python
解决Djang2.0.1中的reverse导入失败的问题
2019/08/16 Python
使用Python发现隐藏的wifi
2020/03/04 Python
python json.dumps中文乱码问题解决
2020/04/01 Python
python如何代码集体右移
2020/07/20 Python
大学生自荐书范文
2013/12/10 职场文书
年度考核自我评价
2014/01/25 职场文书
火车的故事教学反思
2014/02/11 职场文书
文科毕业生自荐书范文
2014/04/17 职场文书
2014年英语工作总结
2014/12/20 职场文书
新郎结婚保证书
2015/02/26 职场文书
2016高一新生军训心得体会
2016/01/11 职场文书
2016廉洁教育心得体会
2016/01/20 职场文书
2016年学校综治宣传月活动总结
2016/03/16 职场文书
小喇叭开始广播了! 四十多年前珍贵老照片
2022/05/09 无线电