今天看了很多篇关于@property装饰器的文章,我对函数的理解有了进一步的加深,这也是面向对象编程进入高阶的第一步,后面还会继续努力。
1. @property装饰器
- 之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴露给外界也是有问题的,比如我们没有办法检查赋给属性的值是否有效。
- 我们之前的建议是将属性命名以: 单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用**@property**包装器来包装getter和setter方法,使得对属性的访问既安全又方便。
- 主要是作用是把类中的一个方法变为类中的一个属性,并且使定义属性和修改现有属性变的更容易
案例1:
- Python内置的@property装饰器可以把类的方法伪装成属性调用的方式。
- 也就是本来是Foo.func()的调用方法,变成Foo.func的方式。
- 一旦给函数加上一个装饰器@property,调用函数的时候不用加括号就可以直接调用函数了。
# 创建一个学生类
class Student:
# 定义学生属性,初始化方法
# name和score属于实例变量, 其中score属于私有变量
def __init__(self, name, score):
self.name = name
self.__score = score
# 利用property装饰器把函数伪装成属性
@property
def score(self):
print("My name is {}, score is {}".format(self.name, self.__score))
# 实例化,创建对象
student1 = Student("AD", 3)
student1.score # My name is AD, score is 3
案例2:
class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!') # integer:整数
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
# 方法1:
s = Student()
s.score = 60 #实际转化为s.set_score(60)
s.score #实际转化为s.get_score()
# 方法2:
s = Student()
s.set_score(90)
s.get_score()
s.set_score(9000)
上面的代码涉及到Pythond的内置函数 isinstance():
- isinstance(object, classinfo) 函数来判断一个对象是否是一个已知的类型,类似 type()
- 如果要判断两个类型是否相同推荐使用 isinstance()
- 如果object的类型与classinfo的类型相同则返回:True,否则返回 False
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value): # birth是可读写属性
self._birth = value
@property
def age(self): # age是只读属性,因为age可以根据birth和当前时间计算出来
return 2015 - self._birth
"""
注意如果直接将属性名作为函数名,那么属性必须设置为私有属性,否则在用s.score时,不知道调用函数,还是调用属性,
就会犯RecursionError: maximum recursion depth exceeded in comparison错误
"""
案例3:
# @perproty装饰器,使得定义新属性和对现有的属性的修改变的更简单
class C(object):
@property
def x(self):
"I am the 'x' property."
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
# 传统的方法绑定属性和访问属性:
class UserInfo(object):
# name属于私有变量
def get_name(self):
"""通过类的方法访问类中的属性"""
return self.__name
def set_name(self, name):
"""通过外部传参的方式绑定属性"""
self.__name = name
if __name__ == '__main__':
user = UserInfo()
# 绑定name属性
user.set_name(["AD"])
print("My name is:", user.get_name()) # My name is: ['AD']
class UserInfo(object):
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
if isinstance(name, str):
self.__name = name
else:
raise TypeError("The name must be str")
if __name__ == '__main__':
user = UserInfo()
# 绑定属性
user.name = "AD"
print("My name is: ", user.name) # My name is: AD
user.name = ["AD"]
print("My name is: ", user.name) # TypeError: The name must be str
- 经过优化后的代码我们可以看到当绑定的属性并非是一个字符串类型时,就会报错,而且我们可以直接通过类似访问属性的方式来绑定属性,访问属性,这样就更加直观了
案例4:
- 可以用来在类的外部访问私有成员属性
class UserInfo(object):
def __init__(self, name, age):
self.__name = name
self.__age = age
if __name__ == '__main__':
user = UserInfo('AD', 18)
print(user.__name) # AttributeError: 'UserInfo' object has no attribute '__name'
# 进行优化之后:
class UserInfo(object):
def __init__(self, name):
self.__name = name
@property
def name(self):
"""通过类的方法访问类中的私有属性"""
return self.__name
if __name__ == '__main__':
user = UserInfo('AD')
print("获取name属性:", user.name)
* 综合案例1 :
class Person(object):
def __init__(self, name, age):
self._name = name
self._age = age
# 1.访问器 - getter方法:
# name为私有变量,无法从外部进行访问
@property
def name(self):
return self._name
# 2.访问器 - getter方法:
@property
def age(self):
return self._age
# 3.修改器 - setter方法:
@age.setter
def age(self, age):
self._age = age
def play(self):
if self._age <= 18:
print('%s正在学Python.' % self._name)
else:
print('%s正在打王者.' % self._name)
def main():
person = Person('李元芳', 17)
person.play() # 李元芳正在学python.
person.age = 22
person.play() # 李元芳正在打王者.
# person.name = '狄仁杰' # AttributeError: can't set attribute
if __name__ == '__main__':
main()
总结
- 1.@property装饰器可以把类的方法伪装成属性调用的方式,如:Foo.func()----Foo.func
- 2.@property装饰器可以用来在类的外部访问私有成员属性
- 需要注意的是
- 被此装饰器装饰的方法不能传递任何除self外的其他参数
- 当同时使用@property和@x.setter时 需要保证x以及被@x.setter修改的方法名字与@property修改的方法名字必须保持一致