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

在这之前,先来讨论一些“坏”的面向对象理论,这些都告诉我们绝不要直接访问属性(如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. fiddler软件测试——Fiddler抓取https设置详解(图文)

    强烈推荐(原创亲测)!!!Fiddler抓取https设置详解(图文)转 本文主要说明了自己在设置fiddler抓取https过程中所遇到的问题及解决步骤,特别是fiddler在设置证书的环节遇到的各 ...

  2. devops工具

    工具类型及对应的不完全列举整理如下: 代码管理(SCM):GitHub.GitLab.BitBucket.SubVersion 构建工具:Ant.Gradle.maven 自动部署:Capistran ...

  3. python之旅5【第五篇】

    装饰器详解 函数刚开始不解析内部,只是放进内存 装饰器是函数,只不过该函数可以具有特殊的含义,装饰器用来装饰函数或类,使用装饰器可以在函数执行前和执行后添加相应操作. 1 下面以一个函数开始,理解下面 ...

  4. Python中的numpy模块解析

    numpy 1.  创建对象 维度(dimensions):轴 轴的个数:秩(rank) Numpy最重要的一个特点就是其N维数组对象(即ndarray) 创建数组最简单的函数就是用array函数: ...

  5. Antenna Placement POJ - 3020 二分图匹配 匈牙利 拆点建图 最小路径覆盖

    题意:图没什么用  给出一个地图 地图上有 点 一次可以覆盖2个连续 的点( 左右 或者 上下表示连续)问最少几条边可以使得每个点都被覆盖 最小路径覆盖       最小路径覆盖=|G|-最大匹配数 ...

  6. Codeforces1073E Segment Sum 【数位DP】

    题目分析: 裸的数位DP,注意细节. #include<bits/stdc++.h> using namespace std; ; int k; ][],sz[][],cnt[][]; ] ...

  7. 【XSY2709】count DP

    题目描述 有一个序列\(A\),你可以随意排列这个序列,设\(s=\sum_{i=1}^{n-1}|a_i-a_{i+1}|\) . 问你最终\(s\leq m\)的方案数有几种. 保证\(A\)中的 ...

  8. 二叉搜索树(BST)详解

    前言:平衡树的前置知识吧 二叉搜索树的定义: 二叉搜索树或者是一棵空树,或者是具有下列性质的二叉树: (1)若左子树不空,则左子树上所有结点的值均小于或等于它的根节点的值: (2)若右子树不空,则右子 ...

  9. 正睿 2019 省选附加赛 Day10

    A 核心就是一个公式 \[\sum_{i = 0}^{k} S(k, i) \tbinom{x}{i} i\] S是第二类斯特林数 递推公式 \(S_2(n,k)=S_2(n−1,k−1)+kS_2( ...

  10. Android RadioGroup中设置默认选中RadioButton 后,选中两个的问题 解决方法

    项目中遇到多个RadioGroup中单选RadioButton ,设置了默认选中第一个 . 然后就 能选中两个RadioButton . . .. 我开始这样给设置默认选中一个的: for (int ...