当前位置: 首页 > news >正文

@property python知乎_Python3基础之:property

0、访问类的成员变量的几种方式

由于某些原因(将在最后一小节详细讨论),当我们外部访问(读写)一个类的成员变量时,一般会通过一组Getter和Setter(获取子和设置子)函数来达到目的,而不是直接访问谇变量。而为了避免使用者直接访问这种类型的成员变量,这些成员变量往往会被声明为私有的。

关于私有变量的讨论,请阅读我往期的文章:酸痛鱼:Python3基础之:私有成员​zhuanlan.zhihu.come7e3858e289d655316534ab1b13f841e.png

在Python3中,Getter和Setter一般有两种方式,一种是直接声明get和set函数,一直则是通过property装饰器(属性)。由于property有两种语法,所以通过property修饰器又有两种不同的实现方式。

在本文中,当我们提到“成员变量”时,就是指直接访问数据成员;当提到“属性”时,就是通过property修饰器访问“成员变量”。“使用getter和setter”及“使用属性”是有区别的,Python中,属性是getter和setter的一种实现方式。本文会严格区分这两种的叫法。

出于介绍property使用的需要,下面的例子中除了实现getter和setter之外,也会实现deleter。

第一种,get和set函数

class Foo:

def __init__(self, v):

self.__val = v

def GetV(self):

'''Member variable __val'''

return self.__val

def SetV(self, v):

self.__val = v

def DelV(self):

del self.__val

第二种,通过property修饰器使用get和set函数

class Foo:

def __init__(self, v):

self.__val = v

def GetV(self):

return self.__val

def SetV(self, v):

self.__val = v

def DelV(self):

del self.__val

v = property(GetV, SetV, DelV, 'Member variable __val')

这种方式是在上一种的基础上进的改造,用户仍然可以使用GetV、SetV、DelV,可以直接使用属性v来达到同样的效果:

f = Foo("bar")

# 如下两行代码是等价的

print(f.GetV())

print(f.v)

# 如下两行代码是等价的

f.SetV("pass gas")

f.v = "pass gas"

# 如下两行代码是等价的

f.DelV()

del f.v

当然,既然使用了属性v,那么原则上GetV、SetV、DelV应该声明为私有的,我们没有充分理由再直接使用GetV、SetV、DelV这三个方法。

第三种,使用get函数修饰器的setter和deleter

class Foo:

def __init__(self, v):

self.__val = v

@property

def v(self):

return self.__val

@v.setter

def v(self, _v):

self.__val = _v

@v.deleter

def v(self):

del self.__val

@v.setter和@v.deleter是可选的。例如,如果我们只想声明只读的属性v,可以这么实现:

class Foo:

def __init__(self, v):

self.__val = v

@property

def v(self):

return self.__val

1、一个完整的实例

在这个实现中,我们实现一个Human类。Human有四个成员变量,即姓名、年龄、体重和性别。我们用上一节介绍的三种方式,分别实现了姓名、年龄、体重的setter、getter、deleter;但因为一个人一旦出生,性别就是不可以改变的(No offence to GLBTQ),所以我们把它声明为了只读属性(readonly property)。

本文是出于教学型的目的编写的,所以混合了各种实现方式。在实际开发中,要保持风格和实现方式的统一,只选其中一种方式就好;另外,property修饰器既然做为一个便利而存在,我个人更推荐第三种实现方式(只读属性是这种方式的特例之一)。

class Gender:

Female = "Female"

Male = "Male"

class Human:

def __init__(self, name, age, weight, gender):

self.__name = name # 名字

self.__age = age # 年龄

self.__weight = weight # 体重

self.__gender = gender # 性别

def GetName(self):

"""Retrive A Human's Name"""

return self.__name

def SetName(self, name):

self.__name = name

def DelName(self):

del self.__name

def GetAge(self):

return self.__age

def SetAge(self, age):

self.__age = age

def DelAge(self):

del self.__age

age = property(GetAge, SetAge, DelAge, \

"We are talking about AGE...")

@property

def weight(self):

return self.__weight

@weight.setter

def weight(self, weight):

self.__weight = weight

@weight.deleter

def weight(self):

del self.__weight

@property

def gender(self):

'''Gender Cannot bo motified'''

return self.__gender

def __str__(self):

fmt = '''Basic Infos:

Name: {}

Age: {}

Weight:{}

Gender:{}

'''

return fmt.format(self.__name, self.__age, self.__weight, self.__gender)

if __name__ == "__main__":

girl = Human("Emily", 17, 45, Gender.Female)

print("Initiated Info:")

print(girl)

girl.SetName("Emilie")

girl.age = girl.age + 1

girl.weight = girl.weight - 1

# gender是只读属性,不可修改

# girl.gender = Gender.Male

print("Modified Info:")

print(girl)

执行结果:

Initiated Info:

Basic Infos:

Name: Emily

Age: 17

Weight:45

Gender:Female

Modified Info:

Basic Infos:

Name: Emilie

Age: 18

Weight:44

Gender:Female

在上面的例子中,我们通过propery修饰器声明了weight属性,虽然它们都是函数,但在实际使用的过程中,它看起来更像一个成员变量,所以要符合成员变更的命名规范。

2、为什么要使用getter和setter

如果使用getter和setter只是为了单纯地访问一个成员变量,那么完全没有必要。

在我们上面的例子中,只要把目标变量变成公有的(严格意义上来说,python没有所谓私有变量),然后直接访问该成员变量就好。

那么,为什么要使用getter和setter呢?

其实这个是基于很多层面上的考虑。在这里,我们举几个常见的场景来说明。在这里列举的不是全部原因,但足以说明getter和setter的重要性。deleter也是出于getter和setter类似的考虑,为了简化表述,下面中将不提及deleter。

A、访问限制控制

通过getter和setter,我们可以灵活地控制一个成员变量的访问限制。如果只有getter,就是只读的;如果只有setter,就是只写的;如果同时有getter和setter,就是可读可写的。

B、值的合法范围限制

如果我们我们知道一个人的生日,那么我们也知道他的年龄;然而年龄每年都是增长一岁;我们可以通过getter动态获取年龄。

我们也知道,女孩子的年龄是不可以超过18岁的,如果我们算出来的年龄超过18,应该返回18。

一个人的体重不可能为0的,它肯定是大于零的。一个新生儿的体重一般不低于2500g。

反正就是,一个值,它总会有它的合理范围的。在某些必要的条件下,当某个值被访问时,我们应该返回一个合理的值;当某个值被修改是,我们应该拒绝不合理的修改。

class Gender:

Female = "Female"

Male = "Male"

class Human:

def __init__(self, name, gender, birth_year, weight):

self.__name = name

self.__gender = gender

self.__by = birth_year

self.__weight = weight

def IsGirl(self):

return (self.__gender == Gender.Female)

@property

def name(self):

'''只读属性'''

return self.__name

@property

def birth_year(self):

'''只读属性'''

return self.__by

@property

def age(self):

'''只读属性'''

this_year = GetYear() # Pseudo function

age = this_year - self.birth_year

if self.IsGirl():

# Girls never age!!!

if age > 18: age = 18

return age

@property

def weight(self):

if self.__weight <= 2500:

return 2500

return self.__weight

@weight.setter

def weight(self, v):

'''体重的合理范围应该在2.5kg和100kg之间'''

if v < 2500 or v > 100000:

### return

raise InvalidWeightException

self.__weight = v

C、线程同步(并发、数据竞争)

在多线程环境中(Python使用GIL,实际上同时只会有一个线程在运行,为了讨论需要,我们假设了一个多线程环境),如果多个线程对同一个变量进行修改,会出现一些意想不到的问题。往往这个时候我们需要进行线程同步。(对线程同步不了解的读者,应该先了解一下线程同步问题。)

如果我们直接使用成员变量,在每个访问这个变量的地方进程同步处理,这将是一个冗余而又不安全的做法。这个时候,通过setter就可以很好的解决问题。

class Foo:

def __init__(self, v):

self.__v = v

self._lock = SomeKindOfLock()

@property

def v(self):

return self.__v

@v.setter(self, _v)

self._lock.lock() # 获取锁

self.__v = _v # 修改数据

self._lock.release() # 释放锁

D、重构与可维护性

由于某种原因,我们需要对原有的某些功能进行重构。这在项目开发中,是非常常见的。

在一开始,我们通过如下的方式实现了Human类。

class Human:

def __init__(self, name, gender, age):

self.__name = name

self.__gender = gender

self.__age = age

@property

def name(self):

return self.__name

@property

def gender(self):

return self.__gender

@property

def age(self):

return self.__age

@age.setter

def age(self, v):

self.__age = v

到这里,一切看起来没什么问题。

在功能(系统)设计的初期,我们往往会对某些问题欠缺考虑,而导致设计上的一些问题。看到这里,相信各位读者已经知道问题在哪了。事实上,人的年龄是会变化的。通过目前的这种实现方式,我们必须每一年都去同步一个每个Human对象的年龄。

于是,我们进行了简单的重构,为Human类设置出生年份字段,而非年龄字段。代码改起来也很简单。我们只需要将构造函数的age参数改为birth_year,并重写一下age属性的getter方法就好了,我们还可以顺便把setter去掉。

class Human:

def __init__(self, name, gender, birth_year):

self.__name = name

self.__gender = gender

self.__by = birth_year

@property

def name(self):

return self.__name

@property

def gender(self):

return self.__gender

@property

def age(self):

age = GetYear() - self.__by

return age

因为我们使用了属性(即使用了getter和setter),这个重构看起来简单而又高效。

哪怕是我们一开始没有使用属性,借助property将age转为属性,代码重构起来也会很简单。

class Human:

def __init__(self, name, gender, age):

self.name = name

self.gender = gender

self.age = age

# 无处不在地使用Human类

jim = Human("Jim", Gender.Male, 20)

print(jim.age)

# 经过一年

jim.age += 1

print(jim.age)

# Lucy想知道jim多少岁

print("Hei lucy, Jim is {} year-old".format(jim.age))

上面我们多次直接使用age成员变量(注意,没有通过getter和setter函数)。如果我们想重构成通过出生年份来动态推算实现年龄,也不会有太多的工作量。

但对于许多编程语言来说,事件并没有那么简单。例如C++和JAVA,如果一开始就直接使用age成员变量,而且age被多次直接使用(读和写),那么这个重构将会是一场灾难。为所有用到age的地方,都必须换成一个函数调用(如GetAge()什么的)。

相关文章:

  • mysql+delete写法_mysql怎么使用delete
  • mysql5.7.19winx64安装_winx64下mysql5.7.19的基本安装流程(详细)
  • 冒险岛062mysql_冒险岛单机版062
  • 了解python web架构_python的几种web框架理解
  • python django框架数据库_Python的Django框架可适配的各种数据库介绍
  • python 删除所有空间_删除dataframe python中的空间
  • python接收前端post数据_Django后台获取前端post上传的文件方法
  • 水经注叠加cad_如何下载矢量格式的CAD等高线
  • JAVA特级_java高级特性-更新版本
  • java字符插入_Java程序练习-字符串插入
  • java 构造 super_java – 在构造函数中的super()
  • java 反射的实现原理图_java反射机制的实现原理
  • java 文件读取 逗号分隔_如何从Java中的文本文件中读取逗号分隔值?
  • java求导数_JAVA实现表达式求导运算的分析总结
  • java模拟登陆系统_Java模拟登录系统抓取内容【转载】
  • 2017-09-12 前端日报
  • electron原来这么简单----打包你的react、VUE桌面应用程序
  • Java 网络编程(2):UDP 的使用
  • leetcode98. Validate Binary Search Tree
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • Material Design
  • Promise面试题2实现异步串行执行
  • Spring核心 Bean的高级装配
  • 海量大数据大屏分析展示一步到位:DataWorks数据服务+MaxCompute Lightning对接DataV最佳实践...
  • 将 Measurements 和 Units 应用到物理学
  • 如何用vue打造一个移动端音乐播放器
  • 深入 Nginx 之配置篇
  • 深入浅出Node.js
  • 线性表及其算法(java实现)
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • # include “ “ 和 # include < >两者的区别
  • #define
  • #define、const、typedef的差别
  • #单片机(TB6600驱动42步进电机)
  • #考研#计算机文化知识1(局域网及网络互联)
  • (14)学习笔记:动手深度学习(Pytorch神经网络基础)
  • (附程序)AD采集中的10种经典软件滤波程序优缺点分析
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (转)setTimeout 和 setInterval 的区别
  • *Django中的Ajax 纯js的书写样式1
  • 、写入Shellcode到注册表上线
  • .net core控制台应用程序初识
  • .NET Core中Emit的使用
  • .NET 应用架构指导 V2 学习笔记(一) 软件架构的关键原则
  • .netcore 如何获取系统中所有session_ASP.NET Core如何解决分布式Session一致性问题
  • .net遍历html中全部的中文,ASP.NET中遍历页面的所有button控件
  • .NET委托:一个关于C#的睡前故事
  • .net专家(高海东的专栏)
  • .Net组件程序设计之线程、并发管理(一)
  • .php文件都打不开,打不开php文件怎么办
  • @synthesize和@dynamic分别有什么作用?
  • [ Linux ] Linux信号概述 信号的产生
  • [ 常用工具篇 ] AntSword 蚁剑安装及使用详解
  • [Android Pro] AndroidX重构和映射
  • [bzoj 3534][Sdoi2014] 重建