cssmagic / Learn-AI-Assisted-Python-Programming

📖 《AI 辅助编程》这本书的大本营。
137 stars 13 forks source link

[译] [301] 初识函数 #36

Open cssmagic opened 6 months ago

cssmagic commented 6 months ago

3.1 Functions

3.1 初识函数

Before we can learn about the details of writing a function, we need some insight into their purpose in software. Functions are small tasks that help accomplish larger tasks, which in turn help solve larger tasks, and so forth. We think you already have a lot of intuition about breaking apart large tasks into smaller tasks that you’ll be able to bring to bear in this example:

在我们深入探讨编写函数的具体细节之前,首先需要洞悉它们在软件开发中的重要性。函数就像是一系列小任务,它们汇聚起来共同完成更宏大的任务,进而解决更为复杂的问题。我们认为,你已经具备了将复杂任务拆解为简单子任务的直觉,这将在以下示例中得到应用:

Suppose that you’ve found a word search puzzle in the newspaper that you’d like to solve (See figure 3.1 for an example puzzle). In these kinds of puzzles, you’re looking for each of the words in the word list. The words can be found going from left to right, right to left, up to down, or down to up.

设想你偶然发现了一份报纸上的单词搜索游戏,正打算挑战一番(详见图 3.1,那里有一个游戏示例)。这类游戏中,你的任务是找出单词列表中的每一个单词。这些单词可能横向从左至右、从右至左,或者纵向从上至下、从下至上地隐藏着。

Figure 3.1 Example Wordsearch puzzle

图 3.1 单词搜索游戏示例

At a high level, your task is “find all of the words in the word search”. Unfortunately, that description of the task isn’t helpful on its own. It doesn’t tell us what steps we need to carry out to solve the problem.

在更宏观的层面,你的任务是“在单词搜索中找出所有单词”。然而,这样的任务描述实际上并不具备指导性。它并未指明我们应该如何具体操作以解决这一问题。

Try working on the problem right now for a couple minutes. How did you start? How did you break down the overall task to make it more achievable?

尝试现在就花几分钟来处理这个问题。你是从哪里着手的?你是如何将整个任务分解,以便更容易完成的?

One thing you might do is say, “OK, finding every word is a big task, but a smaller task is just finding the first word (CAT). Let me work on that first!” This is an example of taking a large task and breaking it into smaller tasks. To solve the entire puzzle, then, you could repeat that smaller task for each word that you need to find.

你可能会这样想:“好吧,找出所有单词确实是个庞大的挑战,但首先找出第一个单词(比如'CAT')则简单多了。我先从这个开始吧!”这是将庞大任务拆解为小任务的一个实际例子。为了完成整个字谜游戏,你只需对每个需要寻找的单词重复这个简化后的任务。

Now, how would we find an individual word, such as CAT? Even this task can be broken down further to make it easier to accomplish. For example, we could break it into four tasks: search for CAT from left to right, search for CAT from right to left, search for CAT from top to bottom, and search for CAT bottom to top. Not only are we making simpler and simpler tasks, but we’re also organizing our work into logical pieces. And, most importantly, as we’ll see throughout the chapter, it’s these simpler tasks whose code we’re going to ask Copilot to write and ultimately assemble into our complete programs.

那么,我们该如何寻找一个特定的单词,例如 "CAT"?其实,这个任务同样可以被进一步细化,以便我们更容易地完成。比如,我们可以将其拆分为四个子任务:从左至右查找 "CAT",从右至左查找 "CAT",从上至下查找 "CAT",以及从下至上查找 "CAT"。我们这样做,不仅使得任务变得简单,而且也让我们的工作更有条理。更为关键的是,正如我们在本章中将不断看到的,正是这些简化后的任务,我们将委托给 Copilot 来编写代码,最终整合成我们完整的程序。

Taking a large problem and dividing it into smaller tasks is called problem decomposition and is one of the most important skills in software design. We’ve dedicated an entire later chapter to it. For now, what is essential is knowing what’s too big of a task to ask Copilot. Asking it to make a new video game that’s a combination of Wordscapes meets Wordle is not going to work at all. However, you can get Copilot to write a function that’s important to solve a larger problem; for example, you might have a function that checks to see if the word provided by the player is present in a list of valid words.

将一个复杂问题拆分成若干小任务,这个过程称为问题分解,它是软件设计中极其关键的技能之一。我们将在后续章节中详细探讨这一主题。目前,你需要了解的是,对于Copilot来说,哪些任务太过庞大。譬如,要求它开发一款融合了Wordscapes和Wordle特点的全新视频游戏是行不通的。但是,你可以指导Copilot编写一些关键的函数,以帮助解决更广泛的问题。例如,你可以设计一个函数,用以验证玩家输入的单词是否包含在有效单词的列表中。

Copilot can solve that problem well and that function would help Copilot get closer to being able to solve the larger problem.

Copilot 能够有效处理这个难题,而这个函数将助 Copilot 一臂之力,使其更接近解决更复杂问题的目标。

3.1.1 The components of a function

3.1.1 函数的组成部分

The origin of the name “function” goes back to math where functions define the output of something based on an input. For example, if f(x) = x2 we can say that when x is 6, f(x) is 36. As programming functions also have expected output for a particular input, the name is quite apt for programming as well.

函数(function)这一术语源自数学,它描述了基于输入定义输出的数学概念。举个例子,f(x) = x^2 表示当 x 等于 6 时,f(x) 的结果为 36。同样,在编程中,函数也是根据给定的输入来产生预期的输出,因此“函数”这一称呼在编程领域同样适用。

As software engineers, we also like to think of functions as promises or contracts. If there is a function called “larger” and we’re told that it takes two numbers and gives us the larger of the two, we have faith that when we give the function the numbers 2 and 5, it will return the answer of 5. We don’t need to see how that function works to be able to use it, any more than we need to know how the mechanics of a car works to be able to use the brake pedal. Press the brake, the car slows down. Give the function two numbers, it gives us back the larger of the two.

作为软件工程师,我们倾向于将函数视作一种承诺或契约。假设存在一个名为“larger”的函数,它接受两个数字并返回其中较大的一个。我们确信,当我们向这个函数提供数字2和5时,它会返回5作为结果。我们在使用这个函数时,并不必要了解其内部工作原理,这与我们使用汽车刹车踏板而无需了解汽车机械构造是同样的道理。按下刹车,汽车便会减速。向函数传递两个数字,它便会返回较大的那个。

Every function in Python has a function header (also called a signature), which is the first line of code of the function. Given their ubiquitous nature, we’ll want to be able to read and write headers. The function header describes the name of the function and its inputs. In some other languages, it sometimes includes some information about what the output looks like, but in Python you have to find that elsewhere in the code.

在 Python 中,每个函数都拥有一个函数头,亦称为签名,它构成了函数代码的第一行。鉴于函数头的普遍性,我们自然需要学会如何阅读和书写它们。函数头详细说明了函数的名称和所需的输入参数。而在其他一些编程语言中,函数头有时还会包含输出结果的相关信息,但在 Python 中,这些信息需要在代码的其他部分寻找。

In Chapter 2, we wrote # comments to tell Copilot what to do. We can continue to use that approach if we want Copilot to generate a function. For example, we can use comments to ask Copilot to write a function that tells us which of two numbers is biggest:

在第二章,我们通过编写 # 注释来指导 Copilot 如何行动。若我们期望 Copilot 帮我们创建一个函数,这种方法仍然适用。举个例子,我们可以在注释中要求 Copilot 编写一个函数,用以比较两个数字并找出较大的那个:

# write a function that returns the larger of two numbers
# input is two numbers
# output is the larger of the two numbers
def larger(num1, num2):
    if num1 > num2:
        return num1
    else:
        return num2
# 编写一个比较两个数字大小并返回较大者的函数
# 输入为两个数字
# 输出为这两个数字中较大的一个
def larger(num1, num2):
    if num1 > num2:
        return num1
    else:
        return num2

As with the code in the last chapter, we just wrote the comments to prompt Copilot to give us the code. The function header has three main components: the keyword that tells Python that this is a function, the name of the function, and the inputs to the function. The word def denotes that this is creating (defining) a function. After def is the name of the function; that name should describe the behavior of the function as well as possible. The name of this function is larger. If it’s hard to name a function because it does a bunch of different things, that’s usually a clue that it’s too big a task for a single function, but more on that later.

与上一章的代码类似,我们刚才写下的注释是为了引导 Copilot 为我们生成代码。函数头包含三个核心部分:一个关键字,用来告诉 Python 这是一个函数;函数的名称;以及函数的输入参数。def 是创建(定义)函数的关键字。def 后面的是函数的名称,这个名字应该尽可能准确地描述函数的功能。这个函数的名字叫做 larger。如果给函数命名感觉困难,因为它涉及到多种不同的操作,这通常意味着这个任务对于一个函数来说太复杂了,我们稍后会进一步讨论这个问题。

What appears in the parentheses are the parameters. Parameters are how you provide information to a function that it needs to run. A function can have any number of parameters and some functions have no parameters. This function has two parameters named num1 and num2; there are two parameters because it needs to know the two numbers it’s comparing.

括号内的内容即为参数。参数是我们向函数传递执行所需信息的途径。函数可以包含任意数量的参数,某些函数也可能不包含任何参数。本例中的函数包含两个参数,分别命名为 num1num2;之所以有两个参数,是因为函数需要明确它所比较的两个数值。

There can be only one output of a function; the keyword to look for when determining what the function is returning is return. Whatever follows the word return is the output of the function. In this code, either num1 or num2 will be returned. Functions are not required to return anything (e.g., a function that prints a list to the screen has no reason to return anything) so if you don’t see a return statement, it isn’t necessarily a problem as the function may be doing something else (interacting with the user, for example) rather than returning something. Functions also either have to return something or not return something, they can’t return something in some cases and nothing in other cases.

函数的输出是唯一的;要了解函数返回的内容,关键是要找到 return 关键字。return 后面的部分即为函数的输出结果。在这段代码示例中,num1num2 将作为返回值。函数并不强制要求必须返回结果(例如,一个打印列表到屏幕的函数就无需返回任何内容),因此即使没有看到 return 语句,也不一定意味着有问题,函数可能在执行其他操作(例如与用户交互)。函数要么返回值,要么不返回,不能时而返回时而不返回。

Although we had Copilot generate this function using # comments, this approach is actually a lot of work for Copilot. It first has to get the header right, including figuring out how many parameters you need. Then it has to get the actual code of the function right.

尽管我们通过 # 注释让 Copilot 为我们生成了这个函数,但这种做法对 Copilot 来说确实颇为繁琐。它首先得精确地编写出函数头,这包括确定所需的参数数量。接着,它还得确保函数的代码正确无误。

There’s an alternate way to prompt Copilot to write the code for a function that may help it generate code more accurately and may help us better understand exactly what we want our function to do. It involves writing a docstring, and we’ll use docstrings to write functions for the majority of the book.

有一种不同的方式可以引导 Copilot 编写函数代码,这不仅能帮助它更精确地生成代码,还能让我们更清楚地了解我们期望函数实现的功能。这种方法就是编写文档字符串,我们将在本书中广泛使用文档字符串来定义函数。

Docstrings explain function behavior

Docstrings are how Python functions are described by programmers. They follow the function header and begin and end with three quotation marks.

Docstrings 解释函数的行为

Docstrings 是程序员用来描述 Python 函数的行为的。它们紧跟在函数头之后,以三个引号开始和结束。

By writing the header and docstring, you’ll make it easier for Copilot to generate the right code. In the header, you will be the one deciding on the name of the function and will provide the names of each parameter that you want the function to use. After the function header, you’ll provide a docstring which tells Copilot what the function does. Then, just as before, Copilot will generate the code for the function. Because we gave Copilot the function header, it will be able to learn from the header and is less likely to make mistakes.

编写函数头和文档字符串,能让 Copilot 更精准地编写代码。在函数头部分,你需要确定函数的名称,并指定你希望函数使用的各个参数的名称。紧接着函数头,编写一个文档字符串来向 Copilot 阐述函数的功能。之后,Copilot 会像之前一样生成函数的代码。由于我们已经提供了函数头,Copilot 能够基于此学习,减少出错的几率。

Here’s what the alternate approach would look like when writing that same larger function:

这里展示了如何采用另一种方式来编写那个 larger 函数:

def larger(num1, num2):
"""
num1 and num2 are two numbers.

Return the larger of the two numbers.
"""
    if num1 > num2:
        return num1
    else:
        return num2
def larger(num1, num2):
"""
num1 和 num2 是两个数字。

返回它们当中较大的那个。
"""
    if num1 > num2:
        return num1
    else:
        return num2

Notice that we wrote the function header as well as the docstring; Copilot supplied the body of the function.

请注意,我们不仅编写了函数头,还包括了文档字符串;而 Copilot 负责生成函数的具体内容。

3.1.2 Using a function

3.1.2 使用函数

Once we have a function, how do we use it? Thinking back to our f(x) = x2 analogy, how do we give the function a value of x that is 6 so that it returns to us 36? Let’s see how to do this with code by using that larger function we just wrote.

一旦我们创建了函数,我们应该如何运用它呢?让我们回想一下之前提到的 f(x) = x^2 的例子,我们如何设定 x 的值为 6,以便函数返回结果 36?接下来,我们将通过我们刚刚编写的这个更复杂的函数来演示这个过程。

The way to use a function is to call it. Calling a function means to invoke the function on specific values of parameters. These parameter values are called arguments. Each value in Python has a type, and we need to take care to give values of the proper type. For example, that larger function is expecting two numbers; it might not work in the expected way if we supply things that aren’t numbers. When we call a function, it runs its code and returns its result. We need to capture that result so that we can use it later, otherwise it will be lost. To capture a result, we use a variable, which is just a name that refers to a value.

要启用一个函数,关键在于调用它。所谓调用,就是根据特定的参数值来执行函数。这些参数值,我们称之为参数。在 Python 编程中,每个值都对应一个类型,我们必须确保传递正确类型的值。比如,那个我们之前编写的比较大小的函数,它需要两个数字作为输入;如果我们传入非数字的值,它可能就不会按预期执行。函数被调用后,会执行其内部代码并返回一个结果。为了之后能够使用这个结果,我们必须将其捕获,否则结果就会丢失。而捕获结果的方式,就是通过一个变量,它本质上是一个指向值的标识。

Here, we ask Copilot to call the function, store the result in a variable, and then print the result.

在这里,我们要求 Copilot 调用函数,将结果存储在一个变量中,然后打印结果。

# call the larger function with the values 3 and 5
# store the result in a variable called result
# then print result
result = larger(3, 5)
print(result)
# 调用 larger 函数,传入参数 3 和 5
# 将返回的结果赋值给变量 result
# 最后打印出 result 的值
result = larger(3, 5)
print(result)

The code correctly calls larger. Notice that it puts the two values we want compared after the opening parenthesis. When the function finishes, it returns a value that we assign to result. Then we print the result. If you run this program, you’ll see that the output 5 gets produced, and that’s because 5 is the larger of the two values that we asked about.

代码正确地调用了 larger 函数。注意,我们将想要比较的两个值放在了开放括号之后。当函数执行完毕后,它会返回一个值,我们将其赋给 result。然后我们打印出结果。如果你运行这个程序,你会看到输出结果是 5,这是因为在我们询问的两个值中,5 是较大的那个。

It’s okay if you aren’t comfortable with all the details here, but what we want you to be able to recognize is when a function is being called, e.g.,

如果你对这些细节还不太熟悉,那完全没关系,但我们希望你至少能够辨识出何时是在调用一个函数,比如:

larger(3, 5)

The general format for a function call is the following:

函数调用的一般格式如下:

function_name(argument1, argument2, argument3, ... )

So, when you see those parentheses right after a name, it means there’s a function call. Calling functions as we did here will be important to our workflow with Copilot, particularly in how we test functions to see if they are working properly. We’ll also need to call functions to get work done, as functions don’t do anything until we call them.

因此,当你看到某个名称后面紧跟着圆括号时,这表示正在进行函数调用。正如我们在此所做的,调用函数对我们与 Copilot 的协作流程极为重要,尤其是在我们对函数进行测试,以验证它们是否按预期工作时。此外,为了完成工作,我们也需要调用函数,因为函数在我们调用它们之前是不会执行任何操作的。