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

在这之前,先来讨论一些“坏”的面向对象理论,这些都告诉我们绝不要直接访问属性(如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. Spring03-AOP

    一. AOP介绍 1. Aop介绍 AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编 ...

  2. 【XSY1262】【GDSOI2015】循环排插 斯特林数

    题目描述 有一个\(n\)个元素的随机置换\(P\),求\(P\)分解出的轮换个数的\(m\)次方的期望\(\times n!\) \(n\leq 100000,m\leq 30\) 题解 解法一 有 ...

  3. 【hihocoder 1424】 Asa's Chess Problem(有源汇上下界网络流)

    UVALive-7670 ICPC北京2016-C题 hihocoder 1424 题意 有个 \(N\times N\) 的棋盘,告诉你每个格子黑色(1)或白色(0),以及每对能相互交换的同行或同列 ...

  4. Qt Creator 中文编译失败 怎么办

    在Qt Creator 中c++源码有中文字符,结果不能编译成功. 代码 QMessageBox::warning(this, "警告","用户名密码错误",Q ...

  5. python的生成器与迭代器和可迭代对象

    来简单的说下python中的生成器和可迭代对象以及迭代器的问题.只是简单地记录一下并不涉及太深入的内容. 首先来说一下什么是生成器,先看下面的代码: #_*_ coding:utf-8 _*_ res ...

  6. 洛谷SP22343 NORMA2 - Norma(分治,前缀和)

    洛谷题目传送门 这题推式子恶心..... 考虑分治,每次统计跨过\(mid\)的所有区间的答案和.\(i\)从\(mid-1\)到\(l\)枚举,统计以\(i\)为左端点的所有区间. 我们先维护好\( ...

  7. 【题解】 AtCoder ARC 076 F - Exhausted? (霍尔定理+线段树)

    题面 题目大意: 给你\(m\)张椅子,排成一行,告诉你\(n\)个人,每个人可以坐的座位为\([1,l]\bigcup[r,m]\),为了让所有人坐下,问至少还要加多少张椅子. Solution: ...

  8. python3 文件和流

    流: 打开文件: open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True,  ...

  9. 利用saltstack批量安装clamav杀毒软件

    源码包安装: clamav_source: file.managed: - name: /tmp/clamav-0.99.2.tar.gz - unless: test -f /tmp/clamav- ...

  10. hexo从零开始

    部署Hexo Hexo官方文档 新建一个文件夹,比如,Blog,然后进入该文件夹下: npm install hexo-cli -g hexo version 安装依赖包 npm install 配置 ...