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获取文件ssdeep值的方法
Oct 05 Python
详细探究Python中的字典容器
Apr 14 Python
Python实现监控程序执行时间并将其写入日志的方法
Jun 30 Python
python搭建虚拟环境的步骤详解
Sep 27 Python
Python ldap实现登录实例代码
Sep 30 Python
详细介绍pandas的DataFrame的append方法使用
Jul 31 Python
python 利用turtle库绘制笑脸和哭脸的例子
Nov 23 Python
Python FFT合成波形的实例
Dec 04 Python
开启Django博客的RSS功能的实现方法
Feb 17 Python
python用WxPython库实现无边框窗体和透明窗体实现方法详解
Feb 21 Python
总结Python使用过程中的bug
Jun 18 Python
python中的class_static的@classmethod的巧妙用法
Jun 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中一些可以替代正则表达式函数的字符串操作函数
2014/11/17 PHP
Laravel实现用户注册和登录
2015/01/23 PHP
详解PHP实现执行定时任务
2015/12/21 PHP
用正则获取指定路径文件的名称
2007/02/27 Javascript
JavaScript OOP类与继承
2009/11/15 Javascript
jQuery获取地址栏参数插件(模仿C#)
2010/10/26 Javascript
基于OO的动画附加插件,可以实现弹跳、渐隐等动画效果 分享
2013/06/24 Javascript
jqGrid日期格式的判断示例代码(开始日期与结束日期)
2013/11/08 Javascript
Java/JS获取flash高宽的具体方法
2013/12/27 Javascript
解析jQuery的三种bind/One/Live事件绑定使用方法
2013/12/30 Javascript
jQuery 回调函数(callback)的使用和基础
2015/02/26 Javascript
JavaScript DSL 流畅接口(使用链式调用)实例
2015/03/15 Javascript
JavaScript定时器和优化的取消定时器方法
2015/07/03 Javascript
JS获取下拉框显示值和判断单选按钮的方法
2015/07/09 Javascript
全面理解JavaScript中的闭包
2016/05/12 Javascript
jQuery简单实现iframe的高度根据页面内容自适应的方法
2016/08/01 Javascript
JS+canvas动态绘制饼图的方法示例
2017/09/12 Javascript
three.js实现3D视野缩放效果
2017/11/16 Javascript
详解js的作用域、预解析机制
2018/02/05 Javascript
vue transition 在子组件中失效的解决
2019/11/12 Javascript
深入分析jQuery.one() 函数
2020/06/03 jQuery
Django实现支付宝付款和微信支付的示例代码
2018/07/25 Python
python Tkinter的图片刷新实例
2019/06/14 Python
使用Python和Scribus创建一个RGB立方体的方法
2019/07/17 Python
用python wxpy管理微信公众号并利用微信获取自己的开源数据
2019/07/30 Python
Flask框架学习笔记之使用Flask实现表单开发详解
2019/08/12 Python
keras.layer.input()用法说明
2020/06/16 Python
简单了解Django项目应用创建过程
2020/07/06 Python
python 基于opencv实现图像增强
2020/12/23 Python
安全生产先进个人总结
2015/02/15 职场文书
2015年效能监察工作总结
2015/04/23 职场文书
初婚未育证明样本
2015/06/18 职场文书
开业典礼致辞
2015/07/29 职场文书
Canvas三种动态画圆实现方法说明(小结)
2021/04/16 Javascript
JavaScript实现班级抽签小程序
2021/05/19 Javascript
SqlServer数据库远程连接案例教程
2021/07/15 SQL Server