Chapter 16 Classes and functions 第 16 章 类与函数
本页译自 Think Python 2e(Allen B. Downey)· Chapter 16 Classes and functions。代码块保留英文原文不翻译;正文段段对照,中文块可用右下角按钮隐藏。
Code examples from this chapter are available from http://thinkpython.com/code/Time1.py.
http://thinkpython.com/code/Time1.py 获取。16.1 Time 16.1 时间
As another example of a user-defined type, we’ll define a class called Time that records the time of day. The class definition looks like this:
Time 的类,用来记录一天中的时间。类的定义如下:Time00008
We can create a new Time object and assign attributes for hours, minutes, and seconds:
Time 对象,并为时、分、秒赋上属性:Time00011
The state diagram for the Time object looks like Figure 16.1.
Time 对象的状态图见图 16.1。Exercise 1
Write a function called Time00014 that takes a Time object and prints it in the form Time00015 . Hint: the format sequence Time00 prints an integer using at least two digits, including a leading zero if necessary.
Time00017 的函数,它接受一个 Time 对象,并以 Time00019 的形式打印它。提示:格式序列 Time00 会打印一个整数,至少用两位数字,必要时前面补零。Exercise 2
Write a boolean function called Time0002 that takes two Time objects, t1 and t1, and returns Time if t1 follows t1 chronologically and Time0 otherwise. Challenge: don’t use an t1 statement.
Time0002,它接受两个 Time 对象 t1 和 t1,如果 t1 在时间上晚于 t1 则返回 Time,否则返回 Time0。挑战:不要使用 t1 语句。Figure 16.1: Object diagram.
16.2 Pure functions 16.2 纯函数
In the next few sections, we’ll write two functions that add time values. They demonstrate two kinds of functions: pure functions and modifiers. They also demonstrate a development plan I’ll call prototype and patch, which is a way of tackling a complex problem by starting with a simple prototype and incrementally dealing with the complications.
Here is a simple prototype of Time0003:
Time0003 的一个简单原型:Time00040
The function creates a new Time object, initializes its attributes, and returns a reference to the new object. This is called a pure function because it does not modify any of the objects passed to it as arguments and it has no effect, like displaying a value or getting user input, other than returning a value.
Time 对象,初始化其属性,并返回指向新对象的引用。这称为「纯函数」,因为它不会修改任何作为实参传入的对象,而且除了返回一个值之外,没有任何副作用,比如显示某个值或获取用户输入。To test this function, I’ll create two Time objects: Time0 contains the start time of a movie, like Monty Python and the Holy Grail, and Time0004 contains the run time of the movie, which is one hour 35 minutes.
Time 对象:Time0 存一部电影的起始时间,比如《蒙提·派森与圣杯》,Time0004 存电影的片长,即 1 小时 35 分钟。Time0004 figures out when the movie will be done.
Time0004 算出了电影何时结束。Time00050
The result, Time0005 might not be what you were hoping for. The problem is that this function does not deal with cases where the number of seconds or minutes adds up to more than sixty. When that happens, we have to “carry” the extra seconds into the minute column or the extra minutes into the hour column.
Time0005 大概不是你想要的。问题在于,当秒数或分钟数相加超过六十时,这个函数并没有处理。发生这种情况时,我们得把多出来的秒「进」到分钟那一列,或者把多出来的分钟「进」到小时那一列。Here’s an improved version:
Time00053
Although this function is correct, it is starting to get big. We will see a shorter alternative later.
16.3 Modifiers 16.3 修改器
Sometimes it is useful for a function to modify the objects it gets as parameters. In that case, the changes are visible to the caller. Functions that work this way are called modifiers.
Time00054, which adds a given number of seconds to a Time object, can be written naturally as a modifier. Here is a rough draft:
Time00056 会给一个 Time 对象加上给定的秒数,它很自然地可以写成一个修改器。下面是一个粗略的草稿:Time00058
The first line performs the basic operation; the remainder deals with the special cases we saw before.
Is this function correct? What happens if the parameter Time000 is much greater than sixty?
Time000 远大于六十,会发生什么?In that case, it is not enough to carry once; we have to keep doing it until Time00061 is less than sixty. One solution is to replace the t1 statements with Time0 statements. That would make the function correct, but not very efficient.
Time00064 小于六十。一个办法是把 t1 语句换成 Time0 语句。这样函数就正确了,但效率并不高。Exercise 3
Write a correct version of Time00067 that doesn’t contain any loops.
Time00068,其中不能包含任何循环。Anything that can be done with modifiers can also be done with pure functions. In fact, some programming languages only allow pure functions. There is some evidence that programs that use pure functions are faster to develop and less error-prone than programs that use modifiers. But modifiers are convenient at times, and functional programs tend to be less efficient.
In general, I recommend that you write pure functions whenever it is reasonable and resort to modifiers only if there is a compelling advantage. This approach might be called a functional programming style.
Exercise 4
Write a “pure” version of Time00069 that creates and returns a new Time object rather than modifying the parameter.
Time00070,它新建并返回一个 Time 对象,而不是修改传入的形参。16.4 Prototyping versus planning 16.4 原型开发与规划
The development plan I am demonstrating is called “prototype and patch.” For each function, I wrote a prototype that performed the basic calculation and then tested it, patching errors along the way.
This approach can be effective, especially if you don’t yet have a deep understanding of the problem. But incremental corrections can generate code that is unnecessarily complicated—since it deals with many special cases—and unreliable—since it is hard to know if you have found all the errors.
An alternative is planned development, in which high-level insight into the problem can make the programming much easier. In this case, the insight is that a Time object is really a three-digit number in base 60 (see Time00072 .)! The Time00 attribute is the “ones column,” the Time00 attribute is the “sixties column,” and the Time attribute is the “thirty-six hundreds column.”
Time 对象其实就是以 60 为基数的三位数(参见 Time00077 )!Time00 属性是「个位」列,Time00 属性是「六十」列,而 Time 属性是「三千六百(六十的平方)」列。When we wrote Time0008 and Time00082, we were effectively doing addition in base 60, which is why we had to carry from one column to the next.
Time0008 和 Time00084 时,实际上是在做以 60 为基数的加法,这正是我们必须从一列向另一列进位的原因。This observation suggests another approach to the whole problem—we can convert Time objects to integers and take advantage of the fact that the computer knows how to do integer arithmetic.
Time 对象转换成整数,并借助计算机本身就会做整数运算这一事实。Here is a function that converts Times to integers:
Time 转换成整数的函数:Time00087
And here is the function that converts integers to Times (recall that Time00 divides the first argument by the second and returns the quotient and remainder as a tuple).
Time 的函数(回想一下,Time00 把第一个实参除以第二个,并以元组形式返回商和余数)。Time00091
You might have to think a bit, and run some tests, to convince yourself that these functions are correct. One way to test them is to check that Time00092 for many values of x. This is an example of a consistency check.
x 值,检查 Time00095 。这是一个一致性检查的例子。Once you are convinced they are correct, you can use them to rewrite Time0009:
Time0009:Time00098
This version is shorter than the original, and easier to verify.
Exercise 5
Rewrite Time00099 using Time00100 and Time00101 .
Time00102 和 Time00103 重写 Time00104。In some ways, converting from base 60 to base 10 and back is harder than just dealing with times. Base conversion is more abstract; our intuition for dealing with time values is better.
But if we have the insight to treat times as base 60 numbers and make the investment of writing the conversion functions (Time00105 and Time00106 ), we get a program that is shorter, easier to read and debug, and more reliable.
Time00107 和 Time00108 ),就能得到一个更短、更易读易调试、也更可靠的程序。It is also easier to add features later. For example, imagine subtracting two Times to find the duration between them. The naive approach would be to implement subtraction with borrowing. Using the conversion functions would be easier and more likely to be correct.
Time 相减来求它们之间的时长。朴素的做法是用借位实现减法;而用转换函数会更简单,也更可能正确。Ironically, sometimes making a problem harder (or more general) makes it easier (because there are fewer special cases and fewer opportunities for error).
16.5 Debugging 16.5 调试
A Time object is well-formed if the values of Time00 and Time00 are between 0 and 60 (including 0 but not 60) and if Time is positive. Time and Time00 should be integral values, but we might allow Time00 to have a fraction part.
Time 对象中,Time00 和 Time00 的值在 0 到 60 之间(含 0 但不含 60),且 Time 为正数,那它就是「格式良好」的。Time 和 Time00 应为整数值,但我们也许允许 Time00 带小数部分。Requirements like these are called invariants because they should always be true. To put it a different way, if they are not true, then something has gone wrong.
Writing code to check your invariants can help you detect errors and find their causes. For example, you might have a function like Time00123 that takes a Time object and returns Time0 if it violates an invariant:
Time00125 这样的函数,它接受一个 Time 对象,若违反了某个不变量就返回 Time0:Time00128
Then at the beginning of each function you could check the arguments to make sure they are valid:
Time00129
Or you could use an Time00 statement, which checks a given invariant and raises an exception if it fails:
Time00 语句,它会检查给定的不变量,一旦不满足就抛出异常:Time00132
Time00 statements are useful because they distinguish code that deals with normal conditions from code that checks for errors.
Time00 语句很有用,因为它把处理正常情况的代码和检查错误的代码区分开来。16.6 Glossary 16.6 术语表
- prototype and patch:
- A development plan that involves writing a rough draft of a program, testing, and correcting errors as they are found.
- planned development:
- A development plan that involves high-level insight into the problem and more planning than incremental development or prototype development.
- pure function:
- A function that does not modify any of the objects it receives as arguments. Most pure functions are fruitful.
- modifier:
- A function that changes one or more of the objects it receives as arguments. Most modifiers are fruitless.
- functional programming style:
- A style of program design in which the majority of functions are pure.
- invariant:
- A condition that should always be true during the execution of a program.
- prototype and patch 原型与打补丁:
- 一种开发方案:先写出程序的粗略草稿,进行测试,并在发现错误时逐一修正。
- planned development 规划式开发:
- 一种开发方案:基于问题的高层洞察进行规划,比渐进式开发或原型开发更有计划性。
- pure function 纯函数:
- 不修改任何作为实参传入的对象的
t1。大多数纯函数都是有返回值的。 - modifier 修改器:
- 会修改一个或多个作为实参传入的对象的
t1。大多数修改器都没有返回值。 - functional programming style 函数式编程风格:
- 一种程序设计风格,其中绝大多数
t1都是纯函数。 - invariant 不变量:
- 在程序执行过程中应当始终为真的条件。
16.7 Exercises 16.7 习题
Code examples from this chapter are available from Time00138 ; solutions to these exercises are available from Time00139 .
Time00140 获取;这些习题的解答可从 Time00141 获取。Exercise 6
Write a function called Time0014 that takes a Time object and a number and returns a new Time object that contains the product of the original Time and the number.
Time0014 的函数,它接受一个 Time 对象和一个数字,返回一个新的 Time 对象,其中包含原 Time 与该数字的乘积。Then use Time0014 to write a function that takes a Time object that represents the finishing time in a race, and a number that represents the distance, and returns a Time object that represents the average pace (time per mile).
Time0014 写一个函数:它接受一个表示比赛中完赛时间的 Time 对象,以及一个表示距离的数字,返回一个表示平均配速(每英里用时)的 Time 对象。Exercise 7
The Time0015 module provides Time and Time objects that are similar to the Date and Time objects in this chapter, but they provide a rich set of methods and operators. Read the documentation at Time00154 .
Time0015 模块提供了 Time 和 Time 对象,与本章的 Date 和 Time 对象相似,但它们提供了丰富的方法和运算符。请阅读文档 Time00158 。- Use the
Time0015 module to write a program that gets the current date and prints the day of the week. - Write a program that takes a birthday as input and prints the user’s age and the number of days, hours, minutes and seconds until their next birthday.
- For two people born on different days, there is a day when one is twice as old as the other. That’s their Double Day. Write a program that takes two birthdays and computes their Double Day.
- For a little more challenge, write the more general version that computes the day when one person is n times older than the other.
- 使用
Time0016 模块写一个程序,获取当前日期并打印星期几。 - 写一个程序,输入一个生日,打印用户的年龄,以及距离下一个生日还有多少天、小时、分钟和秒。
- 对于两个不同日子出生的人,存在一天,其中一人的年龄恰好是另一人的两倍。那就是他们的「双日」(Double Day)。写一个程序,输入两个生日,算出他们的双日。
- 再多一点挑战:写出更通用的版本,算出其中一人年龄是另一人 n 倍的那一天。