提交 ac060eed 编写于 作者: W wizardforcel

ch7.

上级 9e60c95e
#
通过使用 Python 中已有的函数,我们正在建立一个使用的技术清单,用于识别数据集中的规律和主题。 现在我们将探索Python编程语言的核心功能:函数定义。
我们在本书中已经广泛使用了函数,但从未定义过我们自己的函数。定义一个函数的目的是,给一个计算过程命名,它可能会使用多次。计算中有许多需要重复计算的情况。 例如,我们常常希望对表的列中的每个值执行相同的操作。
## 定义函数
`double`函数的定义仅仅使一个数值加倍。
```py
# Our first function definition
def double(x):
""" Double x """
return 2*x
```
我们通过编写`def`来开始定义任何函数。 下面是这个小函数的其他部分(语法)的细分:
当我们运行上面的单元格时,没有使特定的数字加倍,并且`double`主体中的代码还没有求值。因此,我们的函数类似于一个菜谱。 每次我们遵循菜谱中的指导,我们都需要以食材开始。 每次我们想用我们的函数来使一个数字加倍时,我们需要指定一个数字。
我们可以用和调用其他函数完全相同的方式,来调用`double`。 每次我们这样做的时候,主体中的代码都会执行,参数的值赋给了名称`x`
```py
double(17)
34
double(-0.6/4)
-0.3
```
以上两个表达式都是调用表达式。 在第二个里面,计算了表达式`-0.6 / 4`的值,然后将其作为参数`x`传递给`double`函数。 每个调用表达式最终都会执行`double`的主体,但使用不同的`x`值。
`double`的主体只有一行:
```py
return 2*x
```
执行这个`return`语句会完成`double`函数体的执行,并计算调用表达式的值。
`double`的参数可以是任何表达式,只要它的值是一个数字。 例如,它可以是一个名称。 `double`函数不知道或不在意如何计算或存储参数。 它唯一的工作是,使用传递给它的参数的值来执行它自己的主体。
```py
any_name = 42
double(any_name)
84
```
参数也可以是任何可以加倍的值。例如,可以将整个数值数组作为参数传递给`double`,结果将是另一个数组。
```py
double(make_array(3, 4, 5))
array([ 6, 8, 10])
```
但是,函数内部定义的名称(包括像double的x这样的参数)只存在一小会儿。 它们只在函数被调用的时候被定义,并且只能在函数体内被访问。 我们不能在`double`之外引用`x`。 技术术语是`x`具有局部作用域。
因此,即使我们在上面的单元格中调用了`double`,名称`x`也不能在函数体外识别。
```py
x
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-18-401b30e3b8b5> in <module>()
----> 1 x
NameError: name 'x' is not defined
```
文档字符串。 虽然`double`比较容易理解,但是很多函数执行复杂的任务,并且没有解释就很难使用。 (你自己也可能已经发现了!)因此,一个组成良好的函数有一个唤起它的行为的名字,以及文档。 在 Python 中,这被称为文档字符串 - 描述了它的行为和对其参数的预期。 文档字符串也可以展示函数的示例调用,其中调用前面是`>>>`
文档字符串可以是任何字符串,只要它是函数体中的第一个东西。 文档字符串通常在开始和结束处使用三个引号来定义,这允许字符串跨越多行。 第一行通常是函数的完整但简短的描述,而下面的行则为将来的用户提供了进一步的指导。
下面是一个名为`percent`的函数定义,它带有两个参数。定义包括一个文档字符串。
```py
# A function with more than one argument
def percent(x, total):
"""Convert x to a percentage of total.
More precisely, this function divides x by total,
multiplies the result by 100, and rounds the result
to two decimal places.
>>> percent(4, 16)
25.0
>>> percent(1, 6)
16.67
"""
return round((x/total)*100, 2)
percent(33, 200)
16.5
```
将上面定义的函数`percent`与下面定义的函数`percents`进行对比。 后者以数组为参数,将数组中的所有数字转换为数组中所有值的百分数。 百分数都四舍五入到两位,这次使用`round`来代替`np.round`,因为参数是一个数组而不是一个数字。
```py
def percents(counts):
"""Convert the values in array_x to percents out of the total of array_x."""
total = counts.sum()
return np.round((counts/total)*100, 2)
```
函数`percents`返回一个百分数的数组,除了四舍五入之外,它总计是 100。
```py
some_array = make_array(7, 10, 4)
percents(some_array)
array([ 33.33, 47.62, 19.05])
```
理解 Python 执行函数的步骤是有帮助的。 为了方便起见,我们在下面的同一个单元格中放入了函数定义和对这个函数的调用。
```py
def biggest_difference(array_x):
"""Find the biggest difference in absolute value between two adjacent elements of array_x."""
diffs = np.diff(array_x)
absolute_diffs = abs(diffs)
return max(absolute_diffs)
some_numbers = make_array(2, 4, 5, 6, 4, -1, 1)
big_diff = biggest_difference(some_numbers)
print("The biggest difference is", big_diff)
The biggest difference is 5
```
这就是当我们运行单元格时,所发生的事情。
## 多个参数
可以有多种方式来推广一个表达式或代码块,因此一个函数可以有多个参数,每个参数决定结果的不同方面。 例如,我们以前定义的百分比`percents`,每次都四舍五入到两位。 以下两个参的数定义允许不同调用四舍五入到不同的位数。
```py
def percents(counts, decimal_places):
"""Convert the values in array_x to percents out of the total of array_x."""
total = counts.sum()
return np.round((counts/total)*100, decimal_places)
parts = make_array(2, 1, 4)
print("Rounded to 1 decimal place: ", percents(parts, 1))
print("Rounded to 2 decimal places:", percents(parts, 2))
print("Rounded to 3 decimal places:", percents(parts, 3))
Rounded to 1 decimal place: [ 28.6 14.3 57.1]
Rounded to 2 decimal places: [ 28.57 14.29 57.14]
Rounded to 3 decimal places: [ 28.571 14.286 57.143]
```
这个新定义的灵活性来源于一个小的代价:每次调用该函数时,都必须指定小数位数。默认参数值允许使用可变数量的参数调用函数;在调用表达式中未指定的任何参数都被赋予其默认值,这在`def`语句的第一行中进行了说明。 例如,在`percents`的最终定义中,可选参数`decimal_places`赋为默认值`2`
```py
def percents(counts, decimal_places=2):
"""Convert the values in array_x to percents out of the total of array_x."""
total = counts.sum()
return np.round((counts/total)*100, decimal_places)
parts = make_array(2, 1, 4)
print("Rounded to 1 decimal place:", percents(parts, 1))
print("Rounded to the default number of decimal places:", percents(parts))
Rounded to 1 decimal place: [ 28.6 14.3 57.1]
Rounded to the default number of decimal places: [ 28.57 14.29 57.14]
```
## 注:方法
函数通过将参数表达式放入函数名称后面的括号来调用。 任何独立定义的函数都是这样调用的。 你也看到了方法的例子,这些方法就像函数一样,但是用点符号来调用,比如`some_table.sort(some_label)`。 你定义的函数将始终首先使用函数名称,并传入所有参数来调用。
## 在列上应用函数
我们已经看到很多例子,通过将函数应用于现有列或其他数组,来创建新的表格的列。 所有这些函数都以数组作为参数。 但是我们经常打算,通过一个函数转换列中的条目,它不将数组作为它的函数。 例如,它可能只需要一个数字作为它的参数,就像下面定义的函数`cut_off_at_100`
```py
def cut_off_at_100(x):
"""The smaller of x and 100"""
return min(x, 100)
cut_off_at_100(17)
17
cut_off_at_100(117)
100
cut_off_at_100(100)
100
```
如果参数小于或等于 100,函数`cut_off_at_100`只返回它的参数。但是如果参数大于 100,则返回 100。
在我们之前使用人口普查数据的例子中,我们看到变量`AGE`的值为 100,表示“100 岁以上”。 以这种方式将年龄限制在 100 岁,正是`cut_off_at_100`所做的。
为了一次性对很多年龄使用这个函数,我们必须能够引用函数本身,而不用实际调用它。 类似地,我们可能会向厨师展示一个蛋糕的菜谱,并要求她用它来烤 6 个蛋糕。 在这种情况下,我们不会使用这个配方自己烘烤蛋糕, 我们的角色只是把菜谱给厨师。 同样,我们可以要求一个表格,在列中的 6 个不同的数字上调用`cut_off_at_100`
首先,我们创建了一个表,一列是人,一列是它们的年龄。 例如,`C`是 52 岁。
```py
ages = Table().with_columns(
'Person', make_array('A', 'B', 'C', 'D', 'E', 'F'),
'Age', make_array(17, 117, 52, 100, 6, 101)
)
ages
```
| Person | Age |
| --- | --- |
| A | 17 |
| B | 117 |
| C | 52 |
| D | 100 |
| E | 6 |
| F | 101 |
### 应用
要在 100 岁截断年龄,我们将使用一个新的`Table`方法。 `apply`方法在列的每个元素上调用一个函数,形成一个返回值的新数组。 为了指出要调用的函数,只需将其命名(不带引号或括号)。 输入值的列的名称必须是字符串,仍然出现在引号内。
```py
ages.apply(cut_off_at_100, 'Age')
array([ 17, 100, 52, 100, 6, 100])
```
我们在这里所做的是,将`cut_off_at_100`函数应用于`age`表的`Age`列中的每个值。 输出是函数的相应返回值的数组。 例如,17 还是 17,117 变成了 100,52 还是 52,等等。
此数组的长度与`age`表中原始`Age`列的长度相同,可用作名为`Cut Off Age`的新列中的值,并与现有的`Person``Age`列共存。
```py
ages.with_column(
'Cut Off Age', ages.apply(cut_off_at_100, 'Age')
)
```
| Person | Age | Cut Off Age |
| --- | --- | --- |
| A | 17 | 17 |
| B | 117 | 100 |
| C | 52 | 52 |
| D | 100 | 100 |
| E | 6 | 6 |
| F | 101 | 100 |
### 作为值的函数
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册