一、用属性替代 getter 或 setter 方法

以下代码中包含手动实现的 getter(get_ohms) 和 setter(set_ohms) 方法:

class OldResistor(object):

def __init__(self, ohms):

self._ohms = ohms

self.voltage = 0

self.current = 0

def get_ohms(self):

return self._ohms

def set_ohms(self, ohms):

self._ohms = ohms

r0 = OldResistor(50e3)

print(f'Before: {r0.get_ohms()}')

r0.set_ohms(10e3)

print(f'After: {r0.get_ohms()}')

# => Before: 50000.0

# => After: 10000.0

这些工具方法有助于定义类的接口,使得开发者可以方便地封装功能、验证用法并限定取值范围。

但是在 Python 语言中,应尽量从简单的 public 属性写起:

class Resistor(object):

def __init__(self, ohms):

self.ohms = ohms

self.voltage = 0

self.current = 0

r1 = Resistor(50e3)

print(f'Before: {r1.ohms}')

r1.ohms = 10e3

print(f'After: {r1.ohms}')

# => Before: 50000.0

# => After: 10000.0

访问实例的属性则可以直接使用 instance.property 这样的格式。

如果想在设置属性的同时实现其他特殊的行为,如在对上述 Resistor 类的 voltage 属性赋值时,需要同时修改其 current 属性。

可以借助 @property 装饰器和 setter 方法实现此类需求:

from resistor import Resistor

class VoltageResistor(Resistor):

def __init__(self, ohms):

super().__init__(ohms)

self._voltage = 0

@property

def voltage(self):

return self._voltage

@voltage.setter

def voltage(self, voltage):

self._voltage = voltage

self.current = self._voltage / self.ohms

r2 = VoltageResistor(1e3)

print(f'Before: {r2.current} amps')

r2.voltage = 10

print(f'After: {r2.current} amps')

Before: 0 amps

After: 0.01 amps

此时设置 voltage 属性会执行名为 voltage 的 setter 方法,更新当前对象的 current 属性,使得最终的电流值与电压和电阻相匹配。

@property 的其他使用场景

属性的 setter 方法里可以包含类型验证和数值验证的代码:

from resistor import Resistor

class BoundedResistor(Resistor):

def __init__(self, ohms):

super().__init__(ohms)

@property

def ohms(self):

return self._ohms

@ohms.setter

def ohms(self, ohms):

if ohms <= 0:

raise ValueError('ohms must be > 0')

self._ohms = ohms

r3 = BoundedResistor(1e3)

r3.ohms = -5

# => ValueError: ohms must be > 0

甚至可以通过 @property 防止继承自父类的属性被修改:

from resistor import Resistor

class FixedResistance(Resistor):

def __init__(self, ohms):

super().__init__(ohms)

@property

def ohms(self):

return self._ohms

@ohms.setter

def ohms(self, ohms):

if hasattr(self, '_ohms'):

raise AttributeError("Can't set attribute")

self._ohms = ohms

r4 = FixedResistance(1e3)

r4.ohms = 2e3

# => AttributeError: Can't set attribute

要点

优先使用 public 属性定义类的接口,不手动实现 getter 或 setter 方法

在访问属性的同时需要表现某些特殊的行为(如类型检查、限定取值)等,使用 @property

@property 的使用需遵循 rule of least surprise 原则,避免不必要的副作用

缓慢或复杂的工作,应放在普通方法中

二、需要复用的 @property 方法

对于如下需求:

编写一个 Homework 类,其成绩属性在被赋值时需要确保该值大于 0 且小于 100。借助 @property 方法实现起来非常简单:

class Homework(object):

def __init__(self):

self._grade = 0

@property

def grade(self):

return self._grade

@grade.setter

def grade(self, value):

if not (0 <= value <= 100):

raise ValueError('Grade must be between 0 and 100')

self._grade = value

galileo = Homework()

galileo.grade = 95

print(galileo.grade)

# => 95

假设上述验证逻辑需要用在包含多个科目的考试成绩上,每个科目都需要单独计分。则 @property 方法及验证代码就要重复编写多次,同时这种写法也不够通用。

采用 Python 的描述符可以更好地实现上述功能。在下面的代码中,Exam 类将几个 Grade 实例作为自己的类属性,Grade 类则通过 __get__ 和 __set__ 方法实现了描述符协议。

class Grade(object):

def __init__(self):

self._value = 0

def __get__(self, instance, instance_type):

return self._value

def __set__(self, instance, value):

if not (0 <= value <= 100):

raise ValueError('Grade must be between 0 and 100')

self._value = value

class Exam(object):

math_grade = Grade()

science_grade = Grade()

first_exam = Exam()

first_exam.math_grade = 82

first_exam.science_grade = 99

print('Math', first_exam.math_grade)

print('Science', first_exam.science_grade)

second_exam = Exam()

second_exam.science_grade = 75

print('Second exam science grade', second_exam.science_grade, ', right')

print('First exam science grade', first_exam.science_grade, ', wrong')

# => Math 82

# => Science 99

# => Second exam science grade 75 , right

# => First exam science grade 75 , wrong

在对 exam 实例的属性进行赋值操作时:

exam = Exam()

exam.math_grade = 40

Python 会将其转译为如下代码:

Exam.__dict__['math_grade'].__set__(exam, 40)

而获取属性值的代码:

print(exam.math_grade)

也会做如下转译:

print(Exam.__dict__['math_grade'].__get__(exam, Exam))

但上述实现方法会导致不符合预期的行为。由于所有的 Exam 实例都会共享同一份 Grade 实例,在多个 Exam 实例上分别操作某一个属性就会出现错误结果。

second_exam = Exam()

second_exam.science_grade = 75

print('Second exam science grade', second_exam.science_grade, ', right')

print('First exam science grade', first_exam.science_grade, ', wrong')

# => Second exam science grade 75 , right

# => First exam science grade 75 , wrong

可以做出如下改动,将每个 Exam 实例所对应的值依次记录到 Grade 中,用字典结构保存每个实例的状态:

class Grade(object):

def __init__(self):

self._values = {}

def __get__(self, instance, instance_type):

if instance is None:

return self

return self._values.get(instance, 0)

def __set__(self, instance, value):

if not (0 <= value <= 100):

raise ValueError('Grade must be between 0 and 100')

self._values[instance] = value

class Exam(object):

math_grade = Grade()

writing_grade = Grade()

science_grade = Grade()

first_exam = Exam()

first_exam.math_grade = 82

second_exam = Exam()

second_exam.math_grade = 75

print('First exam math grade', first_exam.math_grade, ', right')

print('Second exam math grade', second_exam.math_grade, ', right')

# => First exam math grade 82 , right

# => Second exam math grade 75 , right

还有另外一个问题是,在程序的生命周期内,对于传给 __set__ 的每个 Exam 实例来说,_values 字典都会保存指向该实例的一份引用,导致该实例的引用计数无法降为 0 从而无法被 GC 回收。

解决方法是将普通字典替换为 WeakKeyDictionary:

from weakref import WeakKeyDictionary

self._values = WeakKeyDictionary()

参考资料

python property方法_高效 Python 代码 —— 属性与 @property 方法相关推荐

  1. Python学习笔记(八)—— 私有属性、私有方法、伪私有

    在Python中,以下划线开头的变量名和方法名有特殊的含义,尤其在是在类的定义中.用下划线作为变量名和方法名的前缀和后缀来表示类的特殊成员. _xxx:这样的对象叫做保护成员,只有类对象和子类对象能访 ...

  2. python 时间序列预测_使用Python进行动手时间序列预测

    python 时间序列预测 Time series analysis is the endeavor of extracting meaningful summary and statistical ...

  3. python 概率分布模型_使用python的概率模型进行公司估值

    python 概率分布模型 Note from Towards Data Science's editors: While we allow independent authors to publis ...

  4. python特征选择工具_一个Python特征选择工具,助力实现高效机器学习

    选自GitHub 机器之心编译 参与:Panda 鉴于特征选择在机器学习过程中的重要性,数据科学家 William Koehrsen 近日在 GitHub 上公布了一个特征选择器 Python 类,帮 ...

  5. python预定义_【Python】python类中方法的预定义

    知乎问题: 像这个图片里面显示的,self.prediction首先声明了下然后下面给出了定义,然后optimize又调用这个函数但是没有用self.prediction()这样的方式而是像用变量一样 ...

  6. python私有方法应用场景_Python 私有属性和私有方法应用场景分析

    类的私有属性和方法 Python是个开放的语言,默认情况下所有的属性和方法都是公开的 或者叫公有方法,不像C++和 Java中有明确的public,private 关键字来区分私有公有. Python ...

  7. python dataframe遍历_对Python中DataFrame按照行遍历的方法

    对Python中DataFrame按照行遍历的方法 在做分类模型时候,需要在DataFrame中按照行获取数据以便于进行训练和测试. import pandas as pd dict=[[1,2,3, ...

  8. python中__init__方法_关于python中__init__方法理解

    在理解__init__方法之前,我们需要搞明白,什么时候才需要用到的这个方法 什么是__init__? __init__方法在python中是类的初始化,通俗来讲,就是每次只要你去创建一个类的实例对象 ...

  9. python的property用法_在python中property怎样使用-百度经验

    在python中property可以使类中的方法变为类中的属性一样来使用,使得类中的属性值不易暴露,更加安全,那么在python中property怎样使用呢?下面小编就带大家来看看详细的教程! 工具/ ...

  10. 学python的正确方法_学习Python最正确的步骤(0基础必备)

    首先,学习Python编程技术,自学或者参加培训学习都适用,每个人都有自己的学习方式和方法. 一:明确自己的学习目标. 不管我们学习什么样的知识,都要对自己的学习目标有一个明确的认识.只有这样才能朝着 ...

最新文章

  1. 学术界盛事揭幕:一图解读跨越百余年的诺贝尔奖
  2. 物理生力热实验不确定度计算源代码
  3. Linux操作系统下共享文件夹设置方法介绍
  4. 开源 serverless 产品原理剖析 - Kubeless
  5. 400. 第 N 位数字
  6. 前端学习(2570):template和jsx的对比
  7. python官方的扩展索引_Python列表操作与深浅拷贝(6)——列表索引、查询、修改、扩展...
  8. 一句话讲清楚什么是JavaEE
  9. docker 部分常用镜像下载及安装
  10. 【数字信号处理】数字信号处理简介 ( 数字信号处理技术 | 傅里叶变换 )
  11. 笔记本无线上网怎么通过网线共享给台式机 设置方法
  12. 分享11个网页游戏和9个黑客源码,总有一款适合你
  13. 【读论文0628】Does Learning Require Memorization? A Short Tale about a Long Tail∗
  14. Raspberry PI 常用命令
  15. ValueError: A 0.7-series setuptools cannot be installed with distribute.
  16. sam账号服务器已断开连接,删了sam文件后引发的IIS问题的解决方法
  17. Google Earth Engine——从Python中的经纬度坐标获取Modis正弦图块网格位置
  18. 中国计算机协会(CCF)
  19. iphone文件app里无法连接服务器,苹果商店怎么打不开 无法连接到app store解决方法...
  20. NeurIPS 2022 | MoVQ: 基于Modulating Quantized Vectors的高保真图像生成

热门文章

  1. 【经验总结】js关闭当前页面/关闭当前窗口(兼容所有浏览器)
  2. C/C++ 错误处理
  3. 备份outlook的时候,请不要忘记同时备份Outlook.NK2文件
  4. tomcat的comet事件解释
  5. BizTalk Server 2010 - 使用 WCF Service [ 中篇 ]
  6. python bootstrap 中位数_【机器学习】Bootstrap详解
  7. python行业中性_燃爆!17行Python代码做情感分析?你也可以的
  8. 从键盘录入10个整数,统计有多少个奇数,Java基础轻松实现
  9. idea报错:不支持发行版本5的错误,快速解决方案
  10. 心仪数据拨号服务器无响应,宽带拨号服务器未响应