
3.2.3 实例属性和类属性
Python的类由变量和函数构成,类的变量就是用类实例化对象后对象的属性,类的函数就是用类实例化后对象的方法。类中的变量分为实例属性和类属性,类属性是定义在类的函数之外的变量,而实例属性是定义在类的实例函数之内的变量。实例属性的定义需要在变量名前加入前缀“self.”,例如self.age定义了一个实例属性age。在类外部,用类创建实例后,可以通过“实例名.变量名”的形式访问实例属性,用“实例名.函数名()”的形式调用实例和方法;在类内部,在实例函数中,可以通过“self.变量名”和“self.函数名()”的形式访问实例属性和实例函数。对“self”的理解是,用类实例化对象后,self就是对象本身,就好比函数的形参在定义时的一个占位,等调用函数时,用实参代替形参;self也是类定义时实例对象的一个占位,用类实例化对象后,再用实例对象代替self,因此带有self的变量和函数都是实例的变量(属性)和实例的函数(方法)。
下面我们先分析一下实例属性。下面的程序先定义了一个类person,它有两个类属性nation和party,另外在初始化函数__init__()中定义了两个实例属性name和age,还有一个计数的属性i。类中还有个方法output(),用于输出实例属性name和age。接下来用类创建了两个实例student和teacher,并对实例进行了初始化,赋予了初始值。用类实例化时,会自动执行__init__()方法,student的初始化为name="李明",age=15,teacher的初始化为name="王芳",age=33。接下来第1次调用student和teacher的方法output(),输出实例的属性name和age,可以看出两个实例的属性name和age是不相同的。然后修改student的属性name="李学生",age=18,第2次调用student和teacher的方法output()。从输出结果可以看出,teacher的属性并没有变化,student的属性发生变化,修改student的属性并不影响teacher的属性,这说明实例属性对实例是私有的,不同实例之间的属性是相互独立的,修改一个实例的属性并不影响其他实例的属性值。另外通过类可以看出,实例属性在一个函数中定义后,可以直接在另外一个函数中调用,这个和一般函数的变量是有很大区别的。一般函数的变量是局部变量,不能直接用到其他函数中。需要注意的是,在类的函数中,如果使用了不带“self.”的变量,它将成为函数的局部变量。

下面分析类属性的作用。在类外部,类属性可以用“类名.类变量名”的形式引用。将上面的程序稍作变化,如下面的代码所示,用类person实例化student和teacher后,输出用实例指向的类属性nation和party。从第1次输出结果可以看出,用实例student和teacher指向的实例属性是相同的,然后修改类属性的值,第2次输出的两个实例指向的类属性值也跟着改变了。可以看出,用“类名.类变量名”形式改变类属性的值,将影响所有实例的类属性值,类属性相当于全局属性,类属性影响所有实例的属性,而实例属性只属于单个实例。类属性可以通过“类名.类变量名”形式应用于类的函数体中,这样类属性相当于作用于所有实例的全局变量,而实例属性是只作用于单个实例的局部变量。用类属性可以控制所有的实例,不过建议少用类属性,以便满足封装性的要求。如果需要在类外修改类属性,必须通过类名去引用,然后进行修改。如果通过实例对象去引用类属性,会产生一个与类属性同名的实例属性,这种方式修改的是实例属性副本,不会影响到类属性,并且之后如果通过实例对象去引用该名称的类属性,实例属性会强制屏蔽类属性,即引用的是实例属性,除非删除了该实例属性。类属性也可以用“self.类变量名”的形式在实例函数中引用,这样会产生一个同名的类属性副本。
