Python 系列学习十一:面向对象编程 - 属性控制

前言

打算写一系列文章来记录自己学习 Python 3 的点滴;本章主要介绍 Python 有关面向对象编程中的属性控制的相关内容;

本文为作者的原创作品,转载需注明出处;

属性控制

__slot__ 限制动态扩展范围

动态扩展实例的属性和方法 小节中,我们领略到了任意为实例动态扩展属性和方法的方法,及其的风骚;不过,往往,我们不期望这种任意的行为,因为它太过于风骚了;那么,下面我们该如何来限制它呢?比如,我们只希望对 Student 的实例扩展出属性 name 和 score,而不期望再扩展出其它任何的属性了,该怎么做呢?答案就是使用 __slot__

下面,我们就来重写 Student 类,

1
2
class Student(object):
__slots__ = ('name', 'score')

注意,__slot__ 属性接收一个 tuple 作为对象作为赋值参数;下面,我们再来领略一下 Python 的风骚,

1
2
3
4
5
6
>>> linda = Student()
>>> linda.name = 'Linda Boston'
>>> linda.score = 100
>>> linda.age = 20
Traceback (most recent call last):
File "<stdin>", line 1, in <module>

Ok,当它试图去动态扩展出age属性的时候,报错;看来 __slot__ 已经成功的限制住了 Python 的动态扩展的行为;那么,是否可以动态扩展出新的方法呢?试试,

1
2
>>> def print_score(self):
... print('Student name is %s and sore is %s' % (self.name, self.score))

扩展静态方法,

1
2
3
4
>>> linda.print_score = print_score
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'print_score'

扩展实例方法,

1
2
3
4
5
>>> from types import MethodType
>>> linda.print_score = MethodType(print_score, linda)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'print_score'

由此可以得出结论是,__slot__ 不但可以限制成员变量的动态扩展的行为同样可以限制方法的动态扩展;其实呀,这两个东西本来就是一个东西,成员变量和方法在 Python 统一为属性既是对象;

@property

@property 是一个内置的 装饰器,其作用就是把一个类的方法的访问方式改变为一个属性的访问的方式,来看这样的一个例子,

1
2
3
4
5
6
7
8
9
10
11
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value

@property将方法 score() 的访问方式改变为了属性的访问方式 score,其意义在于,当使用 instance.score 访问该实例属性的时候,将会调用方法 score(self) 并返回的是 self._score 属性值;

@score_setter又是什么呢?起到了什么作用呢?当使用@property装饰器作用于方法 score(self) 的同时,python 解释器又创建了另外一个装饰器,那就是@score_setter,其作用就是就是定义为由经过@property转义后的属性 score 进行赋值的操作;

来看下面的实际操作,

1
2
3
4
5
6
7
>>> linda = Student()
>>> linda.score = 60
>>> linda.score = -1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 10, in score
ValueError: score must between 0 ~ 100!

由此可知,linda.score = 60linda.score = -1将会调用由`@score.setter`所修饰的方法 score(self, value) 来进行赋值的操作,将参数值赋值给实例的私有变量 self._score;所以,当 score 的赋值为 -1 的时候,抛出异常;同时,我们可以使用如下的步骤,来访问实例的 score 变量

1
2
>>> linda.score
60

linda.score实际上调用的就是由@property装饰器所修饰的方法 score(self),并返回私有成员变量 self._score;当然,因为私有变量在 python 中的定义只是约定,所以,我们可以用下面的方式同样可以获得 score 属性值;

1
2
>>> linda._score
60

所以,归纳起来@property装饰器的作用就是为类定义一个成员变量(这里指的就是 self._score )的只读方法,而`@score.setter装饰器的作用就是为该属性定义 _setter_ 方法;Python 这样做的出发点还是为了简便,怎么做怎么简便就怎么做,如果是按照传统的思维,那么首先需要通过构造函数定义一个 *self.\_score* 成员变量,然后再分别定义一个 _getScore()_ 和 _setScore(score)_ 方法来处理 score 的赋值和读取操作,这样做显得繁琐,只要是繁琐的东西,在 Python 中必然得到简化,因此有了@property@score.setter`这样的装饰器;

上述操作,实际上等价于通过@property装饰器为 Student 的实例定义了这样一个可被读写的成员变量 score;那,如果,我只希望定义一个只读属性呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
class Student(object):

@property
def birth(self):
return self._birth

@birth.setter
def birth(self, value):
self._birth = value

@property
def age(self):
return 2015 - self._birth

这样,通过 @property 我们就为 Student 的实例定义了一个只读属性 age