【练习题】第十七章--类和方法(Think Python)

1.方法

重写 is_after(参见16.1),这就比较有难度了,因为这个函数接收两个 Time 对象作为参数。在这个情况下,一般就把第一个参数命名为 self,第二个命名为 other:

# inside class Time:
def is_after(self, other):
    return self.time_to_int() > other.time_to_int()

要使用这个方法,就必须在一个对象后面调用,然后用另外一个对象作为参数:

>>> end.is_after(start)
True

这里就体现出一种语法上的好处了,因为读起来基本就根英语是一样的嗯:『end is after start?』

2.init方法

init 方法(就是对『initialization』的缩写,初始化的意思,这个方法相当于C++中的构造函数)是一种特殊的方法,在对象被实例化的时候被调用。这个方法的全名是init(两个下划线,然后是 init,然后还是两个下划线)。在 Time 类当中,init 方法示例如下:

# inside class Time:
def __init__(self, hour=0, minute=0, second=0):
    self.hour = hour
    self.minute = minute
    self.second = second

一般情况下,init 方法里面的参数与属性变量的名字是相同的。下面这个语句

        self.hour = hour

就存储了参数 hour 的值,赋给了属性变量hour本身。

这些参数都是可选的,所以如果你调用 Time 但不给任何参数,得到的就是默认值。

>>> time = Time()
>>> time.print_time()
00:00:00

如果你提供一个参数,就先覆盖 hour 的值:

>>> time = Time (9)
>>> time.print_time()
09:00:00

提供两个参数,就先后覆盖了 hour 和 minute 的值。

```Python
>>> time = Time(9, 45)
>>> time.print_time()
09:45:00

如果你给出三个参数,就依次覆盖掉所有三个默认值了。

3.str方法

str 是一种特殊的方法,就跟init差不多,str 方法是接收一个对象,返回一个代表该对象的字符串。

例如,下面就是Time 对象的一个 str 方法:

# inside class Time:
def __str__(self):
    return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second)

这样当你用 print 打印输出一个对象的时候,Python 就会调用这个 str 方法:

>>> time = Time(9, 45)
>>> print(time) 09:45:00

写一个新的类的时候,总要先写出来 init 方法,这样有利于简化对象的初始化,还要写个 str 方法,这个方法在调试的时候很有用。 做个练习,写一下 Point 这个类的 str 方法。创建一个 Point 对象,然后用 print 输出一下。

4.运算符重载

通过定义一些特定的方法,咱们就能针对自定义类型,让运算符有特定的作用。比如,如果你在 Time 类中定义了一个名字为add的方法,你就可以对 Time 对象使用『+』加号运算符。

# inside class Time:
def __add__(self, other):
    seconds = self.time_to_int() + other.time_to_int()
    return int_to_time(seconds)

使用方法如下所示:

>>> start = Time(9, 45)
>>> duration = Time(1, 35)
>>> print(start + duration)
11:20:00

当你针对 Time 对象使用加号运算符的时候,Python 就会调用你刚刚自定义的 add 方法。当你用 print 输出结果的时候,Python 调用的是你自定义的 str 方法。所以实际上面这个简单的例子背后可不简单。

针对用户自定义类型,让运算符有相应的行为,这就叫做运算符重载。Python 当中每一个运算符都有一个对应的方法,比如add。更多内容可以看一下https://docs.python.org/3/reference/datamodel.html#specialnames

5.调试

在程序运行的任意时刻都可以给对象增加属性,不过如果你有多个同类对象却又不具有相同的属性,就容易出错了。所以最好在对象实例化的时候就全部用 init 方法初始化对象的全部属性。

如果你不确定一个对象是否有某个特定的属性,你可以用内置的 hasattr 函数来尝试一下(参考15.7)。

另外一种读取属性的方法是用内置函数 vars,这个函数会接收一个对象,然后返回一个字典,字典中的键值对就是属性名的字符串与对应的值。

>>> p = Point(3, 4)
>>> vars(p)
{'y': 4, 'x': 3}

出于调试目的,你估计也会发现下面这个函数随时用一下会带来很多便利:

def print_attributes(obj):
    for attr in vars(obj):
        print(attr, getattr(obj, attr))

内置函数 getattr 会接收一个对象和一个属性名字(以字符串形式),然后返回该属性的值。

6.接口与实现

面向对象编程设计的目的之一就是让软件更容易维护,这就意味着当系统中其他部分发生改变的时候依然能让程序运行,然后可以修改程序去符合新的需求。

实现这一目标的程序设计原则就是要让接口和实现分开。对于对象来说,这就意味着一个类包含的方法要不能被属性表达方式的变化所影响。

比如,在本章我们建立了一个表示一天中时间的类。该类提供的方法包括 time_to_int, is_after, 和 add_time。

我们可以用几种不同方式来实现这些方法。这些实现的细节依赖于我们如何去表示时间。在本章,一个 Time 对象的属性为时分秒三个变量。

还有一种替代方案,我们就可以把这些属性替换为一个单个的整形变量,表示从午夜零点到当前时间的秒的数目。这种实现方法可以让一些方法更简单,比如 is_after,但也让其他方法更难写了。

当你创建一个新的类之后,你可能会发现有更好的实现方式。如果一个程序的其他部位在用你的类,这时候再来改造接口可能就要消耗很多时间,也容易遇到很多错误了。

但如果你仔细地设计好接口,你在改变实现的时候就不用去折腾了,这就意味着你程序的其他部位都不需要改动了。

猜你喜欢

转载自blog.csdn.net/qq_29567851/article/details/83621600