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 22 Python
python字符类型的一些方法小结
May 16 Python
python 时间信息“2018-02-04 18:23:35“ 解析成字典形式的结果代码详解
Apr 19 Python
在python中利用KNN实现对iris进行分类的方法
Dec 11 Python
神经网络相关之基础概念的讲解
Dec 29 Python
Django 响应数据response的返回源码详解
Aug 06 Python
Python连接字符串过程详解
Jan 06 Python
python 已知一个字符,在一个list中找出近似值或相似值实现模糊匹配
Feb 29 Python
自定义Django默认的sitemap站点地图样式
Mar 04 Python
浅谈python opencv对图像颜色通道进行加减操作溢出
Jun 03 Python
python如何发送带有附件、正文为HTML的邮件
Feb 27 Python
python生成随机数、随机字符、随机字符串
Apr 06 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中static静态变量的使用方法详解
2010/06/04 PHP
PHP与C#分别格式化文件大小的代码
2011/05/14 PHP
php设计模式 Template (模板模式)
2011/06/26 PHP
Yii2实现增删改查后留在当前页的方法详解
2017/01/13 PHP
PHP自定义递归函数实现数组转JSON功能【支持GBK编码】
2018/07/17 PHP
汉化英文版的Dreamweaver CS5并自动提示jquery
2010/11/25 Javascript
JS简单的轮播的图片滚动实例
2013/06/17 Javascript
JavaScript分秒倒计时器实现方法
2015/02/02 Javascript
js实现搜索框关键字智能匹配代码
2020/03/26 Javascript
JavaScript生成验证码并实现验证功能
2016/09/24 Javascript
Angular和百度地图的结合实例代码
2016/10/19 Javascript
JS自定义混合Mixin函数示例
2016/11/26 Javascript
JavaScript实现公历转农历功能示例
2017/02/13 Javascript
浅谈vue中关于checkbox数据绑定v-model指令的个人理解
2018/11/14 Javascript
vue + any-touch实现一个iscroll 实现拖拽和滑动动画效果
2019/04/08 Javascript
JS Html转义和反转义(html编码和解码)的实现与使用方法总结
2020/03/10 Javascript
vue中如何自定义右键菜单详解
2020/12/08 Vue.js
python读取oracle函数返回值
2016/07/18 Python
python如何发布自已pip项目的方法步骤
2018/10/09 Python
python实现给scatter设置颜色渐变条colorbar的方法
2018/12/13 Python
Django将默认的SQLite更换为MySQL的实现
2019/11/18 Python
python中栈的原理及实现方法示例
2019/11/27 Python
numpy:np.newaxis 实现将行向量转换成列向量
2019/11/30 Python
浅谈Python中文件夹和python package包的区别
2020/06/01 Python
Python实现验证码识别
2020/06/15 Python
浅谈Python描述数据结构之KMP篇
2020/09/06 Python
详解查看Python解释器路径的两种方式
2020/10/15 Python
python 实现的IP 存活扫描脚本
2020/12/10 Python
Stefania Mode美国:奢华设计师和时尚服装
2018/01/07 全球购物
马来西亚与新加坡长途巴士售票网站:BusOnlineTicket.com
2018/11/05 全球购物
优质飞蝇钓和渔具:RiverBum
2020/05/10 全球购物
办公室文员岗位职责范本
2014/06/12 职场文书
2014年个人工作总结报告
2014/11/27 职场文书
2014大学生学生会工作总结
2014/12/19 职场文书
投标售后服务承诺书
2015/04/29 职场文书