使用property为类中的数据添加行为

时间:2022-09-19 11:43:30

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

在这之前,先来讨论一些“坏”的面向对象理论,这些都告诉我们绝不要直接访问属性(如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&num; 类中发生数据变化的属性信息

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

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

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

  3. C&num;&colon;实体类中做数据验证

    主要是在实体类中验证 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. &period;net EF中从数据添加表或视图时无法添加的问题

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

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

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

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

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

  9. struts中的数据校验

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

随机推荐

  1. HTTP &amp&semi; HTTPs

    HTTP HTTP 消息 HTTP 方法 参考 [1]. HTTP 协议初识 - 阮一峰: HTTPS 参考 [1]. HTTPS 升级指南 - 阮一峰:

  2. javascript实现优先队列

    1.概念 一般情况下从队列中删除元素,都是率先入队的元素.但是有些使用队列的情况不遵循先进先出的原则,这就是插队,这需要使用优选队列的数据结构来进行描述. 从优先队列中删除元素的时候,需要考虑优先级的 ...

  3. mysql查询所有记录,并去掉重复的记录

    select * from tablename group by name;如果是select * from tablename group by name,age;那么查询的是满足name和age都 ...

  4. hdu 1050 &lpar;preinitilization or postcleansing&comma; std&colon;&colon;fill&rpar; 分类: hdoj 2015-06-18 11&colon;33 34人阅读 评论&lpar;0&rpar; 收藏

    errors, clauses in place, logical ones, should be avoided. #include <cstdio> #include <cstr ...

  5. RTX2010服务器端的主要通信端口有哪些?

    RTX服务端程序在安装之后,如果安装服务端电脑的操作系统有防火墙(如Windows XP.Windows2003等)或者安装了防火墙(如瑞星.Norton等),那么需要在防火墙上打开RTX所需要使用的 ...

  6. EF&plus;MVC&plus;cod First项目性能优化总结

    1.EF:this.Configuration.UseDatabaseNullSemantics = true; //关闭数据库null比较行为 2.实体必填字段要加:[Required]属性,可定长 ...

  7. fastboot 刷system&period;img 提示 sending &&num;39&semi;system&&num;39&semi; &lpar;&ast;KB&rpar;&period;&period;&period; FAILED &lpar;remote&colon; data too large&rpar;

    华为G6-C00卡刷提示OEMSBL错误,只能线刷 ,但是官方找不到线刷img镜像,无奈 网上下了个可以线刷的工具套件 流氓ROM . 使用HuaweiUpdateExtractor(工具百度)把官方 ...

  8. 页面全屏显示JS代码

    1.直接在页面加载时就全屏. <body onload="window.open(document.location,'big','fullscreen=yes'):window.cl ...

  9. ios开发的frame、物理屏幕尺寸和图片分辨率

    型号 屏幕尺寸(inch) 逻辑分辨率(point) 缩放因子(scale factor) 物理分辨率(pixel) 像素密度(PPI) iPhone3GS 3.5 320 * 480 @1x 320 ...

  10. 深入理解JVM&lpar;五&rpar;JVM优化策略

    5.2一些案例: 1.高性能硬件部署策略: (1)背景:某公司升级了硬件(CPU升级为4核,内存增加为16G),发现不定期出现网页失去响应. (2)原因:①内存增加之后,项目中有在内存中处理文件的大对 ...