如果你有一个 Person 类(其中3个属性:first_name、second_name、full_name):
class Person(object):
def __init__(self, first_name, second_name):
self.first_name = first_name
self.second_name = second_name
self.full_name = first_name + ' ' + second_name
现在来实例化这个类:
jim = Person('Jim', 'Hancs')
现在可以输出实例 jim 的 first_name、second_name、full_name 3个属性:
print(jim.first_name) # Jim
print(jim.second_name) # Hancs
print(jim.full_name) # Jim Hancs
现在你可以任意给这三个属性任意赋值,并输出:
jim.first_name = 'Tom'
jim.second_name = 'Lily'
jim.full_name = 'Pig'
print(jim.first_name) # Tom
print(jim.second_name) # Lily
print(jim.full_name) # Pig
这显然是不科学的,首先一个人的全名怎么不等于他的姓氏+名字呢?其次一个人的名字可以乱起,但姓氏也可以乱改吗,这不是乱认爹吗?^_^
那咱们第一步就不让他去乱认爹,一出生就让他只跟一个爹(姓氏只能初始化,不能更改值)^_^
class Person(object):
def __init__(self, first_name, second_name):
self.__first_name = first_name # 我们用私有成员变量 __first_name 来存储姓氏,
# 由于用户不能直接访问类的私有成员变量,姓氏就被隐藏起来了,
# 只能根据类提供的接口来对其进行操作(访问,赋值,删除)
self.second_name = second_name
self.full_name = first_name + ' ' + second_name
def get_first_name(self):
return self.__first_name
first_name = property(get_first_name) # property 接收三个参数,参数为函数名,
# 当访问 first_name 时,调用第一个参数
# 当给 first_name 赋值时调用第二个参数的函数
# 当删除 first_name 变量时,调用第三个参数的函数
# 也可以用关键字参数
现在可以输出三个值,可以给后两个属性任意赋值,不能给 first_name 赋值:
jim = Person('Jim', 'Hancs')
print(jim.first_name) # Jim(当访问first_name时会找到property()中找到第一个参数的函数并调用)
print(jim.second_name) # Hancs
print(jim.full_name) # Jim Hancs
jim.second_name = 'Lily'
jim.full_name = 'Pig'
jim.first_name = 'Tom' # 报错:AttributeError: can't set attribute
# 因为我们给 property() 传第二个参数,它找不到对应的函数去调用
第二步就让他的全名只能等于姓氏+名字:
class Person(object):
def __init__(self, first_name, second_name):
self.__first_name = first_name
self.second_name = second_name
self.full_name = first_name + ' ' + second_name
def get_first_name(self):
return self.__first_name
def get_full_name(self):
return self.__first_name + ' ' + self.second_name
def set_full_name(self, name):
if ' ' not in name:
print('请用空格隔开 first_name 和 second_name')
return
first_name, second_name = name.split(' ')
if first_name != self.__first_name:
print('可你不能乱认爹哦~,请用你原来的姓氏!')
return
self.second_name = second_name
first_name = property(get_first_name)
full_name = property(get_full_name, set_full_name)
测试:
jim = Person('Jim', 'Hancs')
print(jim.first_name) # Jim (其实是调用了 jim.get_first_name())
print(jim.second_name) # Hancs
print(jim.full_name) # Jim Hancs(其实是调用了 jim.get_full_name())
jim.second_name = 'Lily'
jim.first_name = 'Tom' # 报错:AttributeError: can't set attribute
# 因为 property() 中没有第二个参数可调用
jim.full_name = 'Pig' # 输出错误信息:请用空格隔开 first_name 和 second_name
jim.full_name = 'Tom pig' # 输出错误信息:可你不能乱认爹哦~,请用你原来的姓氏!
jim.full_name = 'Jim cat' # 执行成功(其实是调用了 jim.set_full_name('jim cat'))
此时,我们只能访问 first_name 的值,却不能给 first_name 赋值,因为 property() 中没有第二个参数。访问 full_name 时,直接访问变量和调用 get_full_name() 效果是一样的。给 full_name 赋值时,直接赋值和调用 set_full_name() 效果也是一样的。这样变量的操作就变得可控,不会被外部随意更改,比如你可以控制名字赋值时长度不能超过20个字符等等,你只要在赋值函数里实现即可。还有一个好处就是,假如你在一个团队项目负责的是这个Person类的实现,别人用你的类去实现功能,假如有一天你心血来潮想优化一下算法,或者改变一下内部 full_name 的实现方式,又或者,你想给函数名换个名字,你改完之后就要通知所有用到你这个类的人去全部更改他们调用的地方,假如又过了两天你又心血来潮,又改了改,你又要让所有用到你这个类的人全部更改,这样下去总有一天你会被他们打死的。而property就不同了,你内部随便怎么改,只要你返回的结果和操作时的检查条件不变,别人就根本不用关心你内部是怎么变化的。不过这样一来,我们的类好像有点乱,我们怎么用更优雅的方式把内部get函数和set函数隐藏呢?接下来我们给它用更 Pythonic 的形式优雅地书写:
class Person(object):
def __init__(self, first_name, second_name):
self.__first_name = first_name
self.second_name = second_name
self.full_name = first_name + ' ' + second_name
@property
def first_name():
def fget(self):
return self.__first_name
return locals()
@property
def full_name():
def fget(self):
return self.__first_name + ' ' + self.second_name
def fset(self, name):
if ' ' not in name:
print('请用空格隔开 first_name 和 second_name')
return
first_name, second_name = name.split(' ')
if first_name != self.__first_name:
print('可你不能乱认爹哦~,请用你原来的姓氏!')
return
self.second_name = second_name
return locals()
这样由于get函数和set函数是在函数内部定义的函数,当超出它的父函数作用域时,函数名变量就已经不存在了,
所以不能再用get函数和set函数来获取变量的值或给变量赋值,
这时对 first_name 和 full_name 的访问和赋值操作和其他变量行为是一样,只是你完全可以对他的访问和赋值操作进行掌控(这是《Python核心编程》里介绍的优雅书写方式,但我这样写测试是报语法错误的,可能是那本书本班比较老了吧)。
我查官方文档应该是这么写的:
class Person(object):
def __init__(self, first_name, second_name):
self.__first_name = first_name
self.second_name = second_name
self.full_name = first_name + ' ' + second_name
@property
def first_name(self):
return self.__first_name
@property
def full_name(self):
return self.__first_name + ' ' + self.second_name
@full_name.setter
def full_name(self, name):
if ' ' not in name:
print('请用空格隔开 first_name 和 second_name')
return
first_name, second_name = name.split(' ')
if first_name != self.__first_name:
print('可你不能乱认爹哦~,请用你原来的姓氏!')
return
self.second_name = second_name
这样能达到同样的效果,first_name 和 full_name 的行为和普通变量一样,只是变得更可操控,是不是很完美呢,这一切皆来源于 property()。