使用property为类中的数据添加行为
对于面向对象编程特别重要的是,关注行为和数据的分离。
在这之前,先来讨论一些“坏”的面向对象理论,这些都告诉我们绝不要直接访问属性(如Java):
class Color:
def __init__(self, rgb_value, name):
self._rgb_value = rgb_value
self._name = name def set_name(self, name):
self._name = name def get_name(self):
return self._name
前缀有一个单下划线的变量表明他们是类私有的,接着get和set方法提供了对每个变量的访问方式,这个类在实际使用中一般采用如下的方式:
>>> c = Color('#ff0000', 'bright red')
>>> c.get_name()
'bright red'
>>> c.set_name('red')
>>> c.get_name()
'red'
这并不像python喜欢的直接访问方式具有可读性:
class Color:
def __init__(self, rgb_value, name):
self.rgb_value = rgb_value
self.name = name
调用如下:
>>> c = Color('#ff0000', 'bright red')
>>> print(c.name)
bright red
>>> c.name = "red"
>>> print(c.name)
red
Java的这种方式方便在需要这些变量被赋值时添加额外的代码,例如我们想要验证输入值是否合理,则可以改变set_name()方法来实现:
def set_name(self, name):
if not name:
raise Expception("Invalid Name")
self._name = name
但是这样会有一个问题,采用直接访问属性方法的代码,现在必须通过调用方法才能访问原有的属性,如果他们不改变自己的访问方式,那么代码就被破坏了。
而在python中可以使用property关键字来处理该问题,加入我们原本使用直接成员访问的方法取访问属性,之后我们可以增加几个方法,在不改变访问接口的情况下,来对name这个变量进行取值和赋值。
class Color:
def __init__(self, rgb_value, name):
self.rgb_value = rgb_value
self._name = name def _set_name(self, name):
if not name:
raise Exception("Invalid Name")
self._name = name def _get_name(self):
return self._name name = property(_get_name, _set_name)
先将name这个属性改为一个(半)私有的_name属性,接着我们添加两个(半)私有方法对这个变量进行取值和赋值,并在赋值的时候进行验证。最后我们在代码底部使用property关键字进行声明。
现在Color类拥有了一个全新的name属性,这个name属性变为了一个property属性,需要通过调用我们刚刚添加的两个方法才能访问或者改变其值。而Color类仍能以前一个版本中相同的方式来使用,同时它还能支持对name赋值时进行验证。
>>> c = Color('#ff0000', 'bright red')
>>> print(c.name)
bright red
>>> c.name = 'red'
>>> print(c.name)
red
>>> c.name =""
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "color2.py", line 8, in _set_name
raise Exception("Invalid Name")
Exception: Invalid Name
这样,我们之前编写的任何代码仍然能够工作,但是,即便name变成了property属性,也不能保证100%的安全,如果有人使它设定为空字符串值,仍然可以通过直接访问_name属性的方式来达到目的。
property是怎样工作的
property函数实际上返回了一个对象,该对象通过我们指定的方法代理了全部对属性值访问或赋值的请求。
property构造函数实际上还可以接受两个额外的参数:一个删除函数和一个property的文本字符串。在实际中很少用到删除函数,但是如果需要用到记录所删除的值,那么删除函数还是很有用的。同时在我们满足某个条件的情况下,删除函数还可以否决删除操作。文本字符串是一个用来描述该property的字符串。如果我们不提供文本字符串这个参数,那么该值将从property的第一个参数,也就是getter方法的文本字符串中复制过来。
这里有个例子,说明了什么时候哪个方法被调用了:
class Silly:
def _get_silly(self):
print("You are getting silly")
return self._silly
def _set_silly(self, value):
print("You are making silly {}".format(value))
self._silly = value
def _del_silly(self):
print("Whoah, you killed silly!")
del self._silly
silly = property(_get_silly, _set_silly,
_del_silly, "This is a silly property")
调用如下:
>>> s = Silly()
>>> s.silly = "funny"
You are making silly funny
>>> s.silly
You are getting silly
'funny'
>>> del s.silly
Whoah, you killed silly!
实际上,property通常只定义两个函数就可以了:getter函数和setter函数。
创建property的另一种方法
property函数本身也可以使用装饰器语法来使一个get函数变成property函数的参数。使用装饰器只需要为函数名添加一个@符号作为前缀,并把结果放在被装饰函数的定义之前就可以了。
class Foo:
@property
def foo(self):
return "bar"
上面的用法是property成为了一个装饰器,这相当于foo = property(foo)。我们还可以为这个property指定一个setter函数:
class Foo:
@property
def foo(self):
return self._foo @foo.setter
def foo(self, value): # 与上面的函数名是一样的
self._foo = value
首先装饰了foo方式,使得它成为getter。接着用刚装饰过的foo方式的setter属性又装饰一个新方法,这个新方法的名字和刚装饰过的foo方法竟然是一样的。property函数返回的是一个对象,而这个对象被自动设置为拥有一个setter属性,而这个setter可以设置为装饰器去装饰其他的函数。
何时该使用property属性
python中数据、property属性、方法都是类的属性。方法只是一个可调用的属性(相当于动词),property属性也只是一个能帮助我们进行决策的自定义属性。 数据属性和property属性应该都是名词,数据属性和property属性之间唯一的区别,就是当property属性被检索、赋值或者删除的时候,我们可以自动调用一些自定义的动作。
假如有个定制化行为的普遍需求,它要求对那些难以计算或者查找起来花费多大的值(例如一个网络请求或者数据库查询)进行缓存。我们的目的是本地存储这个值以便面重复调用那些花费过大的计算。我们可以通过在property属性中使用自定义的getter来达到这个目的。当该值第一次被检索的时候,我们执行查找或计算。接着就可以将这个值以对象中的私有属性的形式缓存在本地。之后,当再次请求这个值时,我们就可以返回缓存的数据。
from urllib.request import urlopen
class WebPage: def __init__(self, url):
self.url = url
self._content = None @property
def content(self):
if not self._content:
print("Retrieving New Page...")
self._content = urlopen(self.url).read()
return self._content
我们可以测试这段代码,看看页面是不是只被检索了一次:
>>> import time
>>> webpage = WebPage("http://ccphillips.net/")
>>> now = time.time()
>>> content1 = webpage.content
Retrieving New Page...
>>> time.time() - now
14.74434518814087
>>> now = time.time()
>>> content2 = webpage.content
>>> time.time() - now
2.50469708442688
>>> content2 == content1
True
第一次加载页面内容花费了14s,第二次花费了2s,这只是将文本写入解释器的时间。
自定义的getter对于需要依据对象中其他成员进行就按的属性,也是非常有帮助的。例如,要计算一个整数列表中各元素的平均值:
class AverageList(list):
@property
def average(self):
return sum(self) / len(self)
它集成自list,我们能够轻易获得类列表的行为。通过在类中加入一个property属性,很快我们的列表就可以得到一个平均值属性:
>>> a = AverageList([1,2,3,4])
>>> a.average
2.5
参考:
1、《Python3 面向对象编程》 [加]Dusty Philips 著
使用property为类中的数据添加行为的更多相关文章
- 如何获取 C# 类中发生数据变化的属性信息
一.前言 在平时的开发中,当用户修改数据时,一直没有很好的办法来记录具体修改了那些信息,只能暂时采用将类序列化成 json 字符串,然后全塞入到日志中的方式,此时如果我们想要知道用户具体改变了哪几个字 ...
- postman上传excel,java后台读取excel生成到指定位置进行备份,并且把excel中的数据添加到数据库
最近要做个前端网页上传excel,数据直接添加到数据库的功能..在此写个读取excel的demo. 首先新建springboot的web项目 导包,读取excel可以用poi也可以用jxl,这里本文用 ...
- C#:实体类中做数据验证
主要是在实体类中验证 using System; namespace Jone.Function.attribute{ /// <summary> /// 附加 ...
- 继承类中static数据值
class A{ static int num = 1; public static void Display(){ System.out.println( num ); } } class B ex ...
- 将数据表中的数据添加到ComboBox控件中
实现效果: 知识运用: ComboBox控件的DataSource 属性 //获取或设置ComboBox的数据源 public Object DataResouce{get;set;} //属性值:任 ...
- .net EF中从数据添加表或视图时无法添加的问题
.net 使用EF模式进行开发,添加实体时不能够正常添加 错误描述: .net中在EF文件中添加数据库中已有的表或视图时不能正常添加,在添加时没有任何的错误提示,但是表或视图就一直拉不过来,,保存也没 ...
- Mysql中从一张表中的数据添加到另一张表
A为原表 B为要加入的表$sql="insert into B select * from A where id=$id";
- C++类中静态数据成员MAP如何初始化
conv_xxx.hpp class convolution { ... ... typedef std::map<int, std::string> ConvDtMap; static ...
- struts中的数据校验
1.struts中如何进行数据校验 在每一个Action类中,数据校验一般都写在业务方法中,比如login().register()等.struts提供了数据校验功能.每个继承自ActionSuppo ...
随机推荐
- 一、Java多人博客系统-开篇
作为一个程序员,工作之外的不断学习是必须的.这个项目是我个人课外学习和练手的项目.最开始是一个个人网站.当时发现京东云可以免费部署网站的,就立即写了一个网站,当时就使用jsp技术,可以实现发布博客.评 ...
- Linux下的好用的编辑软件Remarkable
Linux下的好用的编辑软件Remarkable最近着手开始学习Linux,就想着找一款好用的编辑器作笔记,在网上爬了些贴选择了Remarkable.官网崩了,有没有梯子,废了好大力气才装好.于是把资 ...
- 下载模板、Excel导入、导出
下载模板 /// <summary> /// 下载模板 /// </summary> /// <returns></returns> public Ac ...
- Codeforces510 D. Fox And Jumping
Codeforces题号:#510D 出处: Codeforces 主要算法:map+DP 难度:4.6 思路分析: 题意:给出n张卡片,分别有l[i]和c[i].在一条无限长的纸带上,你可以选择花c ...
- 【XSY1545】直径 虚树 DP
题目大意 给你一棵\(n\)个点的树,另外还有\(m\)棵树,第\(i\)棵树与原树的以\(r_i\)为根的子树形态相同.这\(m\)棵树之间也有连边,组成一颗大树.求这棵大树的直径长度. \(n ...
- 【BZOJ3456】轩辕朗的城市规划 无向连通图计数 CDQ分治 FFT 多项式求逆 多项式ln
题解 分治FFT 设\(f_i\)为\(i\)个点组成的无向图个数,\(g_i\)为\(i\)个点组成的无向连通图个数 经过简单的推导(枚举\(1\)所在的连通块大小),有: \[ f_i=2^{\f ...
- Ubuntu解压
tar -zxvf FileName.tar.gz tar -jxvf FileName.tar.bz2 unzip FileName.zip sudo dpkg -i 文件名.deb
- SPOJ divcntk(min25筛)
题意 \(\sigma_0(i)\) 表示 \(i\) 的约数个数 求 \[ S_k(n)=\sum_{i=1}^n\sigma_0(i^k)\pmod {2^{64}} \] 共 \(T\) 组数据 ...
- Codeforces 1045. A. Last chance(网络流 + 线段树优化建边)
题意 给你 \(n\) 个武器,\(m\) 个敌人,问你最多消灭多少个敌人,并输出方案. 总共有三种武器. SQL 火箭 - 能消灭给你集合中的一个敌人 \(\sum |S| \le 100000\) ...
- 从App业务逻辑中提炼API接口
2.1 从App业务逻辑中提炼API接口 业务逻辑思维导图 功能-业务逻辑思维导图 基本功能模块关系 功能模块接口UML(设计出API) 在设计稿标注API 编写API文档 2.2 设计API的要点 ...