@property 装饰只读属性
将一个类的方法用@property装饰一下,就变成了一个只读的属性。
先看下面定义的Student类,该类有一个实例属性name。用其实例化一个对象student1,其name属性为“Xiao ming”。这种情况下,属性name是可以被修改的,比如后面被修改成了“Ding Ding”。
class Student(object):
def __init__(self, student_name):
self.name = student_name
student1 = Student('Xiao ming') #实例化一个对象
student1.name
输出:'Xiao ming'
student1.name = "Ding Ding" #外部修改该对象的name属性
student1.name
输出:'Ding Ding'
有时候我们不希望属性被外部修改,该怎么办?
python不像其他编程语言有“私有属性”。
有人推荐使用单下划线或双下划线方式命名实例属性(即self._name或self__name),但是这两种方法只是python的一个写法习惯,大家默认类中单/双下滑线开头的属性是不希望被外部访问和修改的,但实际上与前面的例子没有半点不同,外部依然可以访问和修改,见下例:
class Student(object):
def __init__(self, student_name):
self._name = student_name #以“_”开头,希望该属性不被外界访问和修改。
student1 = Student('Xiao ming')
student1._name #依然可以访问私有属性
输出:'Xiao ming'
student1._name = "Ding Ding" #依然可以外部修改私有属性
student1._name
输出:'Ding Ding'
上面的问题怎么解决?
用“_”和@property组合能产生私有属性的效果,可以有效保护某些属性不被外部修改
如下例:
第1步(不是必须的),我们用self._name定义属性(即加了“_”,实际上就是做了一个表面上的标记,希望这个属性不被外部修改和访问,但前面说了,实际上这么定义起不到任何保护作用,除了比普通属性多了条下划线,其他并无二致)。
第2步(必须的),定义了一个用@property 装饰的name(self)方法,该方法返回属性self._name。
如果没有被@property装饰,name(self)就是一个常规的类方法而已,但被修饰后,这个方法就不再是一个常规的方法了,而更像是一个属性。为什么这么说呢,正常情况下方法的调用必须带上(),即student1.name(),但被修饰后就不需要带括号了,直接用方法名即可,即用student1.name就可以得到结果,带括号反而会报错,这是不是更像在使用对象的属性,而不是调用方法?
后面企图通过student1.name = "Ding Ding" 和student1.name("Ding Ding")这两种方式修改名字信息,结果都不行了,这就有效保护了student1这个对象的名字一直是“Xiao ming”。
综合上面两点,被@property装饰的方法确实起到了“私有属性”的效果。
有人可能会说,此时通过student1._name = "Ding Ding"依然可以修改,完全正确!但是大部分情况下,我们不会将Student类的定义暴露给使用者,也就是说使用者并不知道类内部self._name这个属性才是真正存储名字信息的属性,即使用者大概率不会写出student1._name = "Ding Ding"这句代码,更可能写出student1.name = "Ding Ding",就算他很牛逼他猜到了student1._name,但设计者可以更牛逼,用更复杂的变量来存储名字信息,比如self._nnname)。其实,设计Student类的人正是希望使用者只能通过student1.name拿到名字信息,而不能修改名字信息。
class Student(object):
def __init__(self, student_name):
self._name = student_name #以“_”开头,希望该属性不被外界访问和修改。
# 装饰只读方法(只读属性)
@property
def name(self):
return self._name
student1 = Student('Xiao ming')
student1.name #以访问属性的方式调用方法,这就是@property产生的作用。
输出:'Xiao ming'
student1.name() #企图带上()进行访问,出错!
出错……
student1.name = "Ding Ding" #企图这样修改名字,报错
出错……
student1.name("Ding Ding") #企图这样修改名字,报错
出错……
根据前面我们知道,使用者如果知道self._name = student_name 这个定义,他还是可以通过student1._name = "Ding Ding"修改名字的。
但如果属性定义时的名称与 @property装饰函数的名称是同一个(上例中不是同一个,_name和name),那么即使知道属性定义名也无法修改,如下例,属性名和方法名都是_name。想通过student1._name = "Ding Ding"修改名字是行不通的:
class Student(object):
def __init__(self, student_name):
self._name = student_name
@property
def _name(self):
return self._name
student1 = Student('Xiao ming')
student1._name = "Ding Ding" #企图修改名字信息,报错
报错……
@property用于某些需要做一定计算的属性
假设定义了一个Circle类,圆有最基本的属性半径(radius)、周长(perimeter)、面积(area)。
如果直接将这三者都作为实例属性进行定义的话,必须先确定半径,然后计算面积和周长,再分别赋值给他们,很容易出错也不方便。
有人说,可以只将半径作为直接属性,而将周长和面积作为方法定义在类中,这样,半径变了,调用对应方法时返回的周长和面积都会变。然而这么做的话,使用时存在一点不一致性,必须带着()去获取面积和周长。明明也是圆的属性,我们更希望可以通过xx.radiu、xx.perimeter、xx.area这种统一的方式获取。
用@property可以实现!
class Circle(object):
def __init__(self, new_radius):
self.radius = new_radius #半径作为直接属性
@property
def perimeter(self): #周长作为私有保护属性
return 2*3.14159*self.radius
@property
def area(self): #面积作为私有保护属性
return 3.14159*self.radius*self.radius
my_circle = Circle(4) #生成一个对象,半径为4
my_circle.radius #获取半径
4
my_circle.perimeter #获取周长
25.13272
my_circle.area #获取面积
50.26544
my_circle.radius = 5 #直接修改半径
my_circle.radius #获取半径
5
my_circle.perimeter #可见周长也随之改变了
31.4159
my_circle.area #可见面积也随之改变了
78.53975
总结@property:
(1)被@property装饰的方法,在调用时不能带(),直接“对象名.方法名”即可;
(2)通常把一些不希望被外部修改,但外部又会访问到的内容放在被@property装饰的方法中,以非直接的方式返回给用户,从而避免这些内容被外部修改,起到保护作用;
(3)被@property装饰的方法的方法名可随意命名,方法第一个参数必须是self,后面也可以加上其他参数,但没什么卵用,因为外部调用时连()都不让带,参数就更不可能传入了。
(4)如果属性名与被@property装饰的函数名完全相同时(self.name和name()),那么在通过“实例名.name”这种方式获取属性时,调用的永远是name()这个方法,而不是self.name这个属性,即方法优先,这也是python保护直接属性的一种机制。
@*.setter 装饰写检验属性
这个装饰器的作用就是将只读属性,变成可写属性。那么问题来了,既可读又可写,那它与普通属性有何差别?差别就是更安全!
直接看例子,被@radius.setter装饰的函数,多了一个参数new_r,这个参数是用户进行属性修改时的参数( if里self._RADIUS = new_r)。与前面用户可以直接修改self._RADIUS的方式不同,这里加了if判断,即对输入信息做了各种判断,从而保证属性不会被随意修改,而是合法修改。
也就是说,通过@*.setter修饰的方法内部修改属性,比直接修改更安全。
class Circle(object):
def __init__(self):
self._RADIUS = 50 #希望不被直接访问和修改的属性
@property
def radius(self): #定义可用于间接获取self._RADIUS属性的只读属性方法
return self._RADIUS
@radius.setter
def radius(self, new_r): #定义写检验属性方法,可用于设置self._RADIUS(带条件判断)
if isinstance(new_r, int) or isinstance(new_r, float):
self._RADIUS = new_r
else:
print("输入的半径不正确!")
my_circle = Circle() #生成一个对象
print('原始半径', my_circle.radius) #输出半径,这是@property的作用
输出:原始半径 50
my_circle.radius = 10 #直接修改半径(输入是正确的值),这是 @radius.setter的作用
print('新半径', my_circle.radius)
输出:新半径 10
my_circle.radius = "1" #直接修改半径(输入是错误的值),这是 @radius.setter的作用
输出:输入的半径不正确!
总结@*.setter:
(1)主要作用在于,修改属性时可以进行一个额外的判断处理,保证不会被随意修改;
(2)除了参数self以外, 还必须另外有且只有一个参数,可以是不定长参数,如下图;
(3)三者名称必须要一致,如下图: