函数与表格
from datascience import *
import matplotlib
path_data = '../../assets/data/'
matplotlib.use('Agg')
%matplotlib inline
import matplotlib.pyplot as plots
plots.style.use('fivethirtyeight')
import numpy as np
我们正在积累使用 Python 中已有的函数来识别数据集中模式和主题的有用技术库。现在,我们将探索 Python 编程语言的一个核心特性:函数定义。
我们已经在本书中大量使用了函数,但从未定义过自己的函数。定义函数的目的是为可能多次应用的计算过程赋予一个名称。在计算中有许多需要重复计算的情况。例如,我们经常想对表格列中的每个值执行相同的操作。
定义一个函数
下面 double 函数的定义简单的将一个数字加倍。
# Our first function definition
def double(x):
""" Double x """
return 2*x
我们通过写 def 来开始任何函数定义。以下是这个小函数其他部分(“语法”)的分解说明:

当我们运行上面的单元格时,并没有某个特定的数字被加倍,double 函数体内部的代码还没有被执行。在这方面,我们的函数类似于一个“食谱”。每次我们按照食谱中的说明操作,都需要从原料开始。每次我们想用函数来加倍一个数字,都需要指定一个数字。
我们可以用与其他函数完全相同的方式调用 double。每次我们这样做时,函数体中的代码就会执行,参数的值被赋予名称 x。
double(17)
34double(-0.6/4)
-0.3上面的两个表达式都是“调用表达式”。在第二个表达式中,首先计算出表达式 -0.6/4 的值,然后作为名为 x 的参数传递给 double 函数。每个调用表达式都会导致 double 的函数体被执行,但使用不同的 x 值。
double 的函数体只有一行:
return 2*x
执行这个“return 语句”会完成 double 函数体的执行并计算出调用表达式的值。
double 的参数可以是任何表达式,只要其值为数字。例如,它可以是一个名称。double 函数不知道也不关心其参数是如何计算或存储的;它唯一的工作是使用传递给它的参数值来执行自己的函数体。
any_name = 42
double(any_name)
84参数也可以是任何可以被加倍的数值。例如,整个数字数组可以作为参数传递给 double,结果将是另一个数组。
double(make_array(3, 4, 5))
array([ 6, 8, 10])然而,在函数内部定义的名称,包括像 double 的 x 这样的参数,只具有短暂的存续期。它们只在函数被调用时被定义,并且只能在函数体内部访问。我们不能在 double 的函数体外部引用 x。技术术语是 x 具有“局部作用域”。
因此,即使在上面的单元格中调用了 double,名称 x 在函数体外部也无法被识别。
x
[0;31m---------------------------------------------------------------------------[0m
[0;31mNameError[0m Traceback (most recent call last)
[0;32m<ipython-input-7-6fcf9dfbd479>[0m in [0;36m<module>[0;34m[0m
[0;32m----> 1[0;31m [0mx[0m[0;34m[0m[0;34m[0m[0m
[0m
[0;31mNameError[0m: name 'x' is not defined
文档字符串。 虽然 double 相对容易理解,但许多函数执行复杂的任务,如果没有解释就很难使用。(你可能已经自己发现了这一点!)因此,一个编写良好的函数有一个能唤起其行为的名称,以及文档。在 Python 中,这被称为“文档字符串”——关于其行为及其参数预期的描述。文档字符串还可以展示函数的示例调用,其中调用前面有 >>>。
文档字符串可以是任何字符串,只要它是函数体中的第一个内容。文档字符串通常使用开头和结尾的三引号定义,这使得字符串可以跨越多行。第一行按照惯例是函数的一个完整但简短的描述,而后续行则为函数的未来用户提供进一步的指导。
下面是名为 percent 的函数的定义,它接受两个参数。该定义包括一个文档字符串。
# 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 进行对比。后者以一个数组作为参数,并将数组中的所有数字转换为占数组总值百分比的形式。所有百分比都四舍五入到两位小数,这次用 np.round 代替了 round,因为参数是一个数组而不是一个数字。
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。
some_array = make_array(7, 10, 4)
percents(some_array)
array([33.33, 47.62, 19.05])理解 Python 执行函数所采取的步骤是有帮助的。为了方便理解,我们在下面的同一个单元格中放入了函数定义和对该函数的调用。
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 函数每次都将数值四舍五入到两位小数。下面的双参数定义允许不同的调用四舍五入到不同的小数位数。
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。
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)。你定义的函数将始终以函数名在前的方式调用,传入所有参数。