13.4 类属性

什么是属性呢?属性就是属于另一个对象的数据或者函数元素,可以通过我们熟悉的句点属性标识法来访问。一些Python类型比如复数有数据属性(实部和虚部),而另外一些,像列表和字典,拥有方法(函数属性)。

有关属性的一个有趣的地方是,当你正访问一个属性时,它同时也是一个对象,拥有它自己的属性,可以访问,这导致了一个属性链,比如,myThing、subThing、subSubThing。等。常见例子如下:

13.4 类属性 - 图1

类属性仅与其被定义的类相绑定,并且因为实例对象在日常OOP中用得最多,实例数据属性是你将会一直用到的主要数据属性。类数据属性仅当需要有更加“静态”数据类型时才变得有用,它和任何实例都无关,因此,这也是为什么下一节被标为高级主题,你可以选读(如果你对静态不熟,它表示一个值,不会因为函数调用完毕而消失,它在每两个函数调用的间隙都存在。或者说,一个类中的一些数据对所有的实例来说,都是固定的。有关静态数据详细内容,见下一小节)。

接下来的一小节中,我们将简要描述,在Python中,方法是如何实现及调用的。通常,Python中的所有方法都有一个限制:在调用前,需要创建一个实例。

13.4.1 类的数据属性

数据属性仅仅是所定义的类的变量。它们可以像任何其他变量一样在类创建后被使用,并且,要么是由类中的方法来更新,要么是在主程序其他什么地方被更新。

这种属性已为OO程序员所熟悉,即静态变量,或者是静态数据。它们表示这些数据是与它们所属的类对象绑定的,不依赖于任何类实例。如果你是一位Java或C++程序员,这种类型的数据相当于在一个变量声明前加上static关键字。

静态成员通常仅用来跟踪与类相关的值。大多数情况下,你会考虑用实例属性,而不是类属性。在后面,我们正式介绍实例时,将会对类属性及实例属性进行比较。

看下面的例子,使用类数据属性(foo):

13.4 类属性 - 图2

注意,上面的代码中,看不到任何类实例的引用。

13.4.2 Methods

1. 方法

方法,比如下面,类MyClass中的myNoActionMethod方法,仅仅是一个作为类定义一部分定义的函数(这使得方法成为类属性)。这表示myNoActionMethod仅应用在MyClass类型的对象(实例)上。这里,myNoActionMethod是通过句点属性标识法与它的实例绑定的。

13.4 类属性 - 图3

任何像函数一样对myNoActionMethod自身的调用都将失败:

13.4 类属性 - 图4

引发了NameError异常,因为在全局名字空间中,没有这样的函数存在。这就告诉你myNoActionMethod是一个方法,表示它属于一个类,而不是全局空间中的名字。如果myNoActionMethod是在顶层作为函数被定义的,那么我们的调用则会成功。

下面展示的是,甚至由类对象调用此方法也失败了。

13.4 类属性 - 图5

13.4 类属性 - 图6

TypeError异常看起来很让人困惑,因为你知道这种方法是类的一个属性,因此,一定很想知道为何为失败吧?接下来将会解释这个问题。

2. 绑定(绑定及非绑定方法)

为与OOP惯例保持一致,Python严格要求,没有实例,方法是不能被调用的。这种限制即Python所描述的绑定概念(binding),在此,方法必须绑定(到一个实例)才能直接被调用。非绑定的方法可能可以被调用,但实例对象一定要明确给出,才能确保调用成功。然而,不管是否绑定,方法都是它所在的类的固有属性,即使它们几乎总是通过实例来调用的。在13.7节中,我们会更深入地探索本主题。

13.4.3 决定类的属性

要知道一个类有哪些属性,有两种方法。最简单的是使用dir()内建函数。另外是通过访问类的字典属性dict,这是所有类都具备的特殊属性之一。看一下下面的例子:

13.4 类属性 - 图7

根据上面定义的类,让我们使用dir()和特殊类属性dict来查看一下类的属性:

13.4 类属性 - 图8

在新式类中,还新增加了一些属性,dir()也变得更健壮。作为比较,可以看下经典类是什么样的:

13.4 类属性 - 图9

从上面可以看到,dir()返回的仅是对象的属性的一个名字列表,而dict返回的是一个字典,它的键(key)是属性名,键值(value)是相应的属性对象的数据值。

结果还显示了MyClass类中两个熟悉的属性,showMyVersion和myVersion,以及一些新的属性。这些属性,docmodule,是所有类都具备的特殊类属性(另外还有dict)。内建的vars()函数接受类对象作为参数,返回类的dict属性的内容。

13.4.4 特殊的类属性

对任何类C,表13.1显示了类C的所有特殊属性:

13.4 类属性 - 图10

根据上面定义的类MyClass,有如下结果:

13.4 类属性 - 图11

name是给定类的字符名字。它适用于那种只需要字符串(类对象的名字),而非类对象本身的情况。甚至一些内建的类型也有这个属性,我们将会用到其中之一来展示name字符串的益处。

类型对象是一个内建类型的例子,它有name的属性。回忆一下,type()返回被调用对象的类型。这可能就是那种我们所说的仅需要一个字符串指明类型,而不需要一个对象的情况。我们能可以使用类型对象的name属性来取得相应的字符串名。如下例示:

13.4 类属性 - 图12

13.4 类属性 - 图13

doc是类的文档字符串,与函数及模块的文档字符串相似,必须紧随头行(header line)后的字符串。文档字符串不能被派生类继承,也就是说派生类必须含有它们自己的文档字符串。

本章后面会讲到,bases用来处理继承,它包含了一个由所有父类组成的元组。

前述的dict属性包含一个字典,由类的数据属性组成。访问一个类属性的时候,Python解释器将会搜索字典以得到需要的属性。如果在dict中没有找到,将会在基类的字典中进行搜索,采用“深度优先搜索”顺序。基类集的搜索是按顺序的,从左到右,按其在类定义时,定义父类参数时的顺序。对类的修改会仅影响到此类的字典;基类的dict属性不会被改动的。

Python支持模块间的类继承。为更清晰地对类进行描述,1.5版本中引入了module,这样类名就完全由模块名所限定。看一下下面的例子:

13.4 类属性 - 图14

类C的全名是“main.C”,比如,source_module.class_name。如果类C位于一个导入的模块中,如mymod,像下面的:

13.4 类属性 - 图15

在以前的版本中,没有特殊属性module,很难简单定位类的位置,因为类没有使用它们的全名。

最后,由于类型和类的统一性,当访问任何类的class属性时,你将发现它就是一个类型对象的实例。换句话说,一个类已是一种类型了。因为经典类并不认同这种等价性(一个经典类是一个类对象,一个类型是一个类型对象),对这些对象来说,这个属性并未定义。