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语句,那时我们会写更严格的测试代码。