对于面向对象编程特别重要的是,关注行为和数据的分离

在这之前,先来讨论一些“坏”的面向对象理论,这些都告诉我们绝不要直接访问属性(如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为类中的数据添加行为的更多相关文章

  1. 如何获取 C# 类中发生数据变化的属性信息

    一.前言 在平时的开发中,当用户修改数据时,一直没有很好的办法来记录具体修改了那些信息,只能暂时采用将类序列化成 json 字符串,然后全塞入到日志中的方式,此时如果我们想要知道用户具体改变了哪几个字 ...

  2. postman上传excel,java后台读取excel生成到指定位置进行备份,并且把excel中的数据添加到数据库

    最近要做个前端网页上传excel,数据直接添加到数据库的功能..在此写个读取excel的demo. 首先新建springboot的web项目 导包,读取excel可以用poi也可以用jxl,这里本文用 ...

  3. C#:实体类中做数据验证

    主要是在实体类中验证 using System; namespace Jone.Function.attribute{        /// <summary>        /// 附加 ...

  4. 继承类中static数据值

    class A{ static int num = 1; public static void Display(){ System.out.println( num ); } } class B ex ...

  5. 将数据表中的数据添加到ComboBox控件中

    实现效果: 知识运用: ComboBox控件的DataSource 属性 //获取或设置ComboBox的数据源 public Object DataResouce{get;set;} //属性值:任 ...

  6. .net EF中从数据添加表或视图时无法添加的问题

    .net 使用EF模式进行开发,添加实体时不能够正常添加 错误描述: .net中在EF文件中添加数据库中已有的表或视图时不能正常添加,在添加时没有任何的错误提示,但是表或视图就一直拉不过来,,保存也没 ...

  7. Mysql中从一张表中的数据添加到另一张表

    A为原表 B为要加入的表$sql="insert into B select * from A where id=$id";

  8. C++类中静态数据成员MAP如何初始化

    conv_xxx.hpp class convolution { ... ... typedef std::map<int, std::string> ConvDtMap; static ...

  9. struts中的数据校验

    1.struts中如何进行数据校验 在每一个Action类中,数据校验一般都写在业务方法中,比如login().register()等.struts提供了数据校验功能.每个继承自ActionSuppo ...

随机推荐

  1. Installing Office Online Server for SharePoint 2016

    Office Online Server is the next version of the Office Web Apps, which allows your users to view and ...

  2. codeforces589I

    Lottery CodeForces - 589I Today Berland holds a lottery with a prize — a huge sum of money! There ar ...

  3. 使用poi将Excel文件转换为data数据

    pom <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http:// ...

  4. BZOJ3724PA2014Final Krolestwo——欧拉回路+构造

    题目描述 你有一个无向连通图,边的总数为偶数.设图中有k个奇点(度数为奇数的点),你需要把它们配成k/2个点对(显然k被2整除).对于每个点对(u,v),你需要用一条长度为偶数(假设每条边长度为1)的 ...

  5. CSS初步学习

    1.选择器: 如果你要在HTML元素中设置CSS样式,你需要在元素中设置"id" 和 "class"选择器. id 选择器 id 选择器可以为标有特定 id 的 ...

  6. D - Mayor's posters POJ - 2528 离散化+线段树 区间修改单点查询

    题意 贴海报 最后可以看到多少海报 思路 :离散化大区间  其中[1,4] [5,6]不能离散化成[1,2] [2,3]因为这样破坏了他们的非相邻关系 每次离散化区间 [x,y]时  把y+1点也加入 ...

  7. 【XSY1515】【GDKOI2016】小学生数学题 组合数学

    题目描述 给你\(n,k,p\)(\(p\)为质数),求 \[ \sum_{i=1}^n\frac{1}{i}\mod p^k \] 保证有解. \(p\leq {10}^5,np^k\leq {10 ...

  8. 【XSY2524】唯一神 状压DP 矩阵快速幂 FFT

    题目大意 给你一个网格,每个格子有概率是\(1\)或是\(0\).告诉你每个点是\(0\)的概率,求\(1\)的连通块个数\(\bmod d=0\)的概率. 最开始所有格子的概率相等.有\(q\)次修 ...

  9. 【BZOJ3379】【USACO2004】交作业 区间DP

    题目描述 数轴上有\(n\)个点,你要从位置\(0\)去位置\(B\),你每秒钟可以移动\(1\)单位.还有\(m\)个限制,每个限制\((x,y)\)表示你要在第\(t\)秒之后(可以是第\(t\) ...

  10. 爬虫_糗事百科(scrapy)

    糗事百科scrapy爬虫笔记 1.response是一个'scrapy.http.response.html.HtmlResponse'对象,可以执行xpath,css语法来提取数据 2.提取出来的数 ...