`
frank-liu
  • 浏览: 1682604 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

python property解读和对比

 
阅读更多

简介

    Python的property属性从表面上来看是一个比较简单的特性,实际上它的实现和一些在工程上的应用里和结合了descriptor等东西。我们这里从一个简单的属性赋值和访问开始一步步的推导。  同时,这里也和一些对应于java里的用法做了一个比较。通过这些比较我们可以看到一些python的典型用法能够带来一定的灵活性。

 

初始代码

    有的时候,我们写一些python的类里,需要定义一些属性,比如如下的代码:

class Person:
    def __init__(self, first_name):
        self.first_name = first_name

    这里的代码再简单不过了,就是设置一个对象里first_name属性。

    仅仅是以上的这么一个简单的代码,我们可以在如下的代码里来使用Person:

>>> person = Person("firstname")
>>> person.first_name
'firstname'
>>> person.first_name = "another name"
>>> person.first_name
'another name'

    因为在python里,所有的属性默认都是public的,所以这里就相当于java里将属性设置成public一样的效果。我们也可以得到一个类似的java类:

public class Person {
    public String firstName;
    public Person(String firstName) {
        this.firstName = firstName;
    }
}

     我们从一般的常识里会有这么一种感觉,就是代码这样写有点不妥。因为将对象的属性都暴露在外面了。而且容易被多个地方修改导致错误。这是一个方面的问题,另外一方面,我们希望代码更加defensive,可能会在里面加入很多限制和检查,比如说传入的对象不要为空了。甚至在有的时候我们需要对传入的属性做一些其他的限制,比如邮件地址的格式,传入数据的长度或者 数字的范围等等。

    这个时候,如果只是一个暴露出来的这么个属性确实不合适了。

 

property和属性封装

    在python里,如果我们要封装一个属性,那么我们会考虑使用property。假设在前面的代码里,我们需要在设置属性的时候检查它的类型,然后对于它的删除操作不支持,我们可以使用如下的代码:

class Person:
    def __init__(self, first_name):
        self.first_name = first_name

    @property
    def first_name(self):
        return self._first_name

    @first_name.setter
    def first_name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._first_name = value

    @first_name.deleter
    def first_name(self):
        raise AttributeError("Can't delete attribute")

    前面的这部分代码,我们定义了一个first_name的属性。这样以后我们每次访问它们的时候,可以通过person.first_name的方式来访问,和前面的使用方法是一样的。唯一不同的就是我们在实现里增加了类型检查。这里的实现也比较有意思,我们需要考虑的几个属性就是读,写和删除。这几个属性都用同样的方法名,唯一不同的就是对属性的读我们是在first_name方法上增加了@property修饰,而写是@first_name.setter,删除则是@first_name.deleter。这些就是python里设置property的套路。有了这些设置,我们使用一些方式来访问属性的时候会产生如下的结果:

>>> a = Person("first_name")
>>> a.first_name
'first_name'
>>> a.first_name = "new name"
>>> a.first_name = 43
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/frank/programcode/python/person.py", line 12, in first_name
    raise TypeError('Expected a string')
TypeError: Expected a string
>>> del a.first_name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/frank/programcode/python/person.py", line 17, in first_name
    raise AttributeError("Can't delete attribute")
AttributeError: Can't delete attribute

   这里正好对应我们定义的各种行为,因为a.first_name对应了设置property的方法,而那里我们设置了参数类型的检查,所以会有这么个异常。而del a.first_name对应我们删除属性的方法,所以才会出现AttributeError。

    当然,我们并不是一定要定义这所有的方法,有时候如果我们只是需要这个property只读的,设置那个@property读方法就可以了。

    针对这个问题,我们虽然修改了python类里面的代码,但是从使用者的角度来说,基本上没有变化。完全看不出来我们是使用了它的property还是我们最开始设置的public attribute。而对于java代码来说呢?这个时候不可避免的,我们就需要设置属性访问方法了,我们要在方法里进行参数检查。那么一个大致对应的代码实现如下:

public class Person {
    private String firstName;
    
    public getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        if(firstName == null)
            throw new IllegalArgumentException();
        this.firstName = firstName;
    }

    public Person(String firstName) {
        this.firstName = firstName;
    }
}

     而这里因为有了参数检查,所以我们使用它们的代码如果原来是直接访问属性的则需要修改为getFirstName和setFirstName了。这也是为什么java里推荐使用get, set方法来访问属性。因为有了这些方法我们可以更加方便的去检查属性的合法性。

 

减少重复

    前面关于property的使用确实比较合理。可是当我们有多个属性的时候呢?比如说,我们类里有first_name, last_name等等几个同样类型的属性。如果我们需要访问他们的话,都采用同样的property来做吗?

    从前面的代码里已经看到,光定义一个property就要扯上3个方法,如果我们有3, 4个这样的属性要设置...其实就算用property还是满无聊的。那么有没有办法来达到这方面的代码重用呢?我们这里需要使用若干个同样的参数,而且对它们的访问以及参数检查都是一样的,每个property里都这么去检查显得太傻。

    在python这里,还有一个办法可以解决,那就是descriptor类。python里descriptor类是做什么的呢?通常来说,当我们访问一个对象里的属性时,可以通过它来做一些定制化的工作。它是怎么来定制的呢?我们先来看对应的定制代码实现:

class String:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise TypeError('Expect a string')
        if len(value) < 4:
            raise AttributeError('Invalid length')
        instance.__dict__[self.name] = value

    def __del__(self, instance):
        del instance.__dict__[self.name]

    上面实现的代码类似于一个类,它定义了__get__, __set__, __del__这几个方法。从官方的文档定义来说,任何一个对象只要实现其中的任何一个方法,它就可以称为descriptor。这里对__get__, __set__, __del__里定义的方法似乎有点难以理解,我们一个个的讨论过来。在python里,如果我们访问一个对象的属性,其默认的访问方式其实就是对这个对象的字典进行get, set和delete操作。比如说我们访问一个对象的属性a.x,在其内部的实现是通过去查看对象a的字典a.__dict__['x'],如果这里找不到对应的属性,则查找type(a).__dict__['x'],这里type(a)相当于a对象的类,如果这里也找不到的话则去查找type(a)的父类。

    所以有了前面这么个过程,我们就可以看到在get里,如果instance为空的话,我们就返回。这里是因为这个方法尝试返回instance,也就是对象实例或者类所对应的__dict__元素。__set__方法里除了做了一个类型检查以外,我还额外的增加了一个对参数长度的检查。

    使用它们的代码如下:
from string import String

class Person:
    first_name = String('first_name')
    last_name = String('last_name')

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name
   这里定义了两个属性,first_name, last_name。定义的Person类反而清爽了许多。使用Person类的代码如下:
>>> p = Person('first_name', 'last_name')
>>> p.first_name = 'new first'
>>> p.first_name
'new first'
>>> p.last_name = 'new last'
>>> p.last_name
'new last'
>>> p.first_name = 'abc'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/frank/programcode/python/string.py", line 15, in __set__
    raise AttributeError('Invalid length')
AttributeError: Invalid length
>>> p.first_name = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/frank/programcode/python/string.py", line 13, in __set__
    raise TypeError('Expect a string')
TypeError: Expect a string
     这种descriptor的方式相当于一个切面重用一样,可以对应于一个类型的参数进行检查。我们可以实现类型参数设置和检查的重用。
    这个时候,如果我们去考虑对应java的实现,则有点麻烦了。我们可能需要将参数合法性检查之类的东西提取出来作为一个方法,然后所有set访问方法来引用它。还有一个潜在的问题就是如果我们是在构造函数里检查呢?那这方法能管用吗?这个时候要达到这个效果就比较麻烦了。

实现细节

    既然在前面的方法里我们使用了@property这个东西,而在前面的一些文章里我们提到过,@xxx修饰到某些方法的时候,它实际上是一个decorator。所以,前面那个设置属性的代码,其实质上是一个类似于如下的代码:

class Person:
    def __init__(self, first_name):
        self.set_first_name(first_name)

    def get_fist_name(self):
        return self._first_name

    def set_first_name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._first_name = value

    def del_first_name(self):
        raise AttributeError("Can't delete attribute")

    name = property(get_first_name, set_first_name, del_first_name)

    这个时候,如果我们访问person.name,就和前面访问属性的效果是一样的。

    另外,这里property的构造参数里对应的是几个方法,分别是get, set, 和delete的。所以,我们可以通过如下的方式看到它对应的原始方法:

>>> Person.first_name.fget
<function Person.first_name at 0x7f493ab8d0d0>
>>> Person.first_name.fset
<function Person.first_name at 0x7f493ab8d158>
>>> Person.first_name.fdel
<function Person.first_name at 0x7f493ab8d1e0>

    从这个角度来说,property修饰就相当于是对3个主要的属性访问方法的封装。有了这个封装,我们每次只要把它套到对应的方法上就可以让它们给分别对上get, set和delete的号了。

 

总结

     property这个看似简单的概念里其实牵涉到了decorator,descriptor等东西以及我们对python对象属性访问的理解。可以说,python里对对象属性的组织和使用的灵活性有时候有点让人无所适从。所以要理解清楚这个东西还是要仔细啊。而且,采用不同的语言,确实对应着不同的使用和思考方式,要转换也不太容易。

 

参考材料

http://stackoverflow.com/questions/6618002/python-property-versus-getters-and-setters
http://tomayko.com/writings/getters-setters-fuxors

http://stackoverflow.com/questions/17330160/python-how-does-the-property-decorator-work

http://blaag.haard.se/What-s-the-point-of-properties-in-Python/

http://www.programiz.com/python-programming/property

http://www.ibm.com/developerworks/library/os-pythondescriptors/

http://martyalchin.com/2007/nov/23/python-descriptors-part-1-of-2/

http://martyalchin.com/2007/nov/24/python-descriptors-part-2-of-2/

分享到:
评论

相关推荐

    全国青少年软件编程等级考试Python标准解读.pdf

    综上所述,全国青少年软件编程等级考试Python标准解读(1-6级)的知识点涵盖了从基础环境的熟悉、Python基本语法规则的掌握、逻辑和计算思维的培养,到具体的编程技能和计算思维的应用。该考试标准旨在提高全国青...

    Python 用Python实现文件对比分析并生成报告 Python源码

    Python 用Python实现文件对比分析并生成报告 Python源码Python 用Python实现文件对比分析并生成报告 Python源码Python 用Python实现文件对比分析并生成报告 Python源码Python 用Python实现文件对比分析并生成报告 ...

    python实现word文件对比工具

    1.将目标文件与模板文件进行样式对比(docx) 2.统计段落、形状、图、表并与其相应的标题进行样式与数量的对比 3.将文件以样式为key进行序列化,...引用模块python-docx 已实现 样式对比 样式相似度评分 导出所有批注

    Python property 介绍1

    Python 的 `property` 内置函数是面向对象编程中的一个重要特性,它允许开发者创建属性的访问器、修改器和删除器,从而实现对类中数据的控制和封装。`property` 不仅可以增强代码的可读性和安全性,还能提供额外的...

    基于python实现用Python实现文件对比分析并生成报告附项目源码分享

    Python 在通用应用程序、自动化插件、网站、网络爬虫、数值分析、科学计算、云计算、大数据和网络编程等领域有着极为广泛的应用,像 OpenStack 这样的云平台就是由 Python 实现的,许多平台即服务(PaaS)产品都支持...

    python基于selenium所截图片的对比操作

    本示例中,我们关注的是一个基于Python和Selenium库进行图片对比的操作。Selenium是一个强大的Web UI自动化测试工具,能够模拟用户行为,如点击按钮、填写表单等。在结合图片对比技术后,它可以帮助我们检测UI在不同...

    python opencv 图片相似度对比

    Python OpenCV 图片相似度对比是使用 Python 语言和 OpenCV 库来实现的图片相似度对比算法。该算法可以比较两张图片的不同,并生成展现不同之处的图片。 图片相似度对比算法 图片相似度对比算法可以分为两步:首先...

    python文件对比 xml、excel

    在Python编程中,处理XML和Excel文件是常见的任务,尤其在数据处理和分析领域。XML(eXtensible Markup Language)是一种结构化数据格式,常用于存储和交换数据,而Excel则是Microsoft Office套件中的一个应用程序,...

    分别基于Transformer和CNN网络实现CIFAR-100数据集分类任务python源码(用于对比).zip

    分别基于Transformer和CNN网络实现CIFAR-100数据集分类任务python源码(用于对比).zip分别基于Transformer和CNN网络实现CIFAR-100数据集分类任务python源码(用于对比).zip分别基于Transformer和CNN网络实现CIFAR-100...

    Python人脸相似度对比

    Python直接使用模型,实现一个简易版的人脸对比,非常有趣! 中途如果报错 error for dlib, 安装Cmake.msi 搞定。 赞!!

    Python-mysqldiff是一款轻量级数据库对比工具同时支持新增表的默认数据导入

    Python-mysqldiff是一款专为数据库管理设计的轻量级工具,主要功能是对比不同版本的MySQL数据库,并生成相应的SQL脚本以实现数据库结构和数据的同步。它简化了数据库版本控制的过程,尤其适用于多版本数据库管理和...

    对比Excel,轻松学习Python数据分析.zip

    Python数据分析对比Excel,是当前许多数据工作者和爱好者选择学习的新方向。Excel作为一款强大的电子表格软件,对于小规模数据处理和简单分析具有直观且高效的优势。然而,随着大数据时代的到来,面对海量数据,...

    一文详述 Python 中的 property 语法

    Python中的`property`语法是一种强大的特性,用于封装和控制对象的属性访问。它允许开发者创建具有getter和setter方法的属性,使得在访问或修改属性值时可以进行额外的逻辑处理,而外部代码看起来仍然像直接操作属性...

    python办公自动化源码集锦-光速对比文件

    总之,Python的办公自动化能力和文件对比功能在日常工作中有着广泛的应用。通过掌握相关的库和技巧,你可以轻松实现文件管理、数据处理的自动化,从而节省大量时间,提高工作质量。这个源码集锦是一个很好的学习资源...

    浅谈Python3识别判断图片主要颜色并和颜色库进行对比的方法

    本篇文章主要探讨的是如何使用Python3识别和分析图像中的主要颜色,并将这些颜色与一个预定义的颜色库进行对比。 首先,我们来介绍一下什么是HSV颜色模型。HSV代表色相(Hue)、饱和度(Saturation)、亮度(Value),它...

    python规则引擎对比报告

    对pyke和pyclips引擎在功能特点、语法结构和性能表现上,做粗浅的对比。入门级、自制文档

    python编写 实现 快速排序 和 二叉树排序 并对比速度

    python编写 实现 快速排序 和 二叉树排序 并对比速度

    python中@property和property函数常见使用方法示例

    主要介绍了python中@property和property函数常见使用方法,结合实例形式分析了Python @property和property函数功能、使用方法及相关操作注意事项,需要的朋友可以参考下

    python的property属性与用法.doc

    总结来说,`property`属性在Python中提供了对类实例属性的控制和封装,它可以简化数据访问,隐藏内部实现,以及执行数据验证和转换。在新式类中,`property`还能结合`setter`和`deleter`进一步增强属性管理能力,...

    对比Excel轻松学习Python数据分析

    理数据—分析数据—得出结论—验证结论—展示结论进行Excel和Python的对比 现,告诉你每一个过程中都会用到什么,过程与过程之间有什么联系。本书既 以作为系统学习数据分析操作流程的说明书,也可以作为一本数据分析师...

Global site tag (gtag.js) - Google Analytics