Python面向对象编程(第4版)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.2.2 让它做点什么

让对象拥有属性已经很棒了,但是面向对象编程的重点在于不同对象之间的交互。我们感兴趣的是,触发某些行为可以使属性发生变化。现在是时候为我们的类添加一些行为了。

让我们模拟Point类的一些动作。我们可以从一个让它回到原点的reset()方法开始(其中原点是指x和y坐标值都是0的点)。这个动作很适合初学者学习,因为它不需要任何参数:

print语句的执行结果显示两个属性值都变成了0:

Python中的方法在格式上和函数完全相同。它以def关键字开始,紧接着的是空格和方法名,然后是一对括号括起来的参数列表(我们稍后马上会讨论self参数,有时其也被称为实例变量),最终以冒号结尾。下一行开始是方法内部的代码块。这些语句可以是任意的Python代码,可以操作对象自身属性和传递给方法的参数。

我们忽略了reset()方法的类型提示,因为这里不是很适合使用类型提示。我们会在2.2.3节看到使用类型提示最好的地方。下面我们先深入看一下实例变量,以及self关键字的工作原理。

和自己对话——self

类方法和普通函数的区别之一是,所有的类方法都有一个必要的参数。按照惯例,这个参数通常被命名为self;我从没见过哪个程序员使用其他名称(习惯是很有力量的)。当然,你可以用this,甚至Maishu等名称,但你最好遵循PEP 8中的编码规范,使用self。

简单来说,self参数就代表被调用的对象本身。这个对象是类的实例,有时候也被称为实例变量。

我们可以通过self访问对象的属性和方法。这也是为什么我们可以在reset()方法中通过self对象设置对象的x和y属性。

在这个讨论中,注意对象的区别。我们可以将方法视为附加到类上的函数。self参数代表类的当前实例。当在两个不同的对象上调用该方法时,调用两次相同的方法,但传递两个不同的对象作为self参数。

注意,当我们调用p.reset()方法时,我们不需要传递self参数给它。Python自动帮我们做了这件事。Python知道我们正在调用p对象的方法,所以它自动将p对象传递给Point类的这个方法。

方法实际上就是一个放在类中的函数。除了调用对象的方法,我们也可以直接通过类名调用函数,同时将对象名作为self参数传递给函数:

输出结果与上一个示例完全一样,因为内部发生的过程是完全一样的。这实在不是好的编程实践,但它能加深我们对self参数的理解。

如果我们忘了在定义方法时添加self参数,会发生什么?Python会抛出一个错误:

这个错误的消息看起来不是那么清楚(如果是“你这个傻瓜,你忘记了self参数”,则可能看起来更直接一些)。只要记住,如果看到一条错误消息说缺少参数,第一件事就应该检查你是否忘记了方法定义中的self参数。

更多参数

我们如何向方法传递多个参数呢?让我们添加一个新的方法,用来将Point对象移动到任意指定的位置,而不仅仅是原点。我们也可以接收另一个Point对象作为输入,并返回它们之间的距离:

我们定义的这个类有两个属性x和y,以及3个方法:move()、reset()及calculate_distance()。

move()方法接收两个参数x和y,并用它们的值设置self对象的坐标。既然reset操作就是把对象移动到特定的已知点,那么用reset()方法直接调用move()方法就可以了。

calculate_distance()方法用于计算两点之间的欧几里得距离(计算距离还有一些其他方法,我们会在第3章的案例学习中讨论其他方法)。我希望你可以理解其中的数学计算。数学公式可以用math.hypot()来实现。在Python中,我们使用self.x,但数学家通常使用xs

下面是一个使用这个类定义的示例。它显示了如何调用一个有参数的方法:使用同样的点号标记法调用实例的方法,并把参数包在括号内。我们只是挑选了几个随机点来测试这些方法。测试代码调用了每个方法并把结果打印到控制台上:

assert(断言)语句是一个神奇的测试工具。如果assert后面的语句的执行结果为False(或者是零、空值、None),那么程序将会报错并退出。在这个示例中,我们用它来确保不管是哪个对象调用另一个对象的calculate_distance()方法,距离应该是相等的。我们会在第13章中更多地使用assert语句,那时我们会写更严格的测试代码。