函数定义

本章我们将介绍编程语言中非常重要的一个概念——函数。这里的函数和我们之前在数学中所学习的函数,有一些相似点,也有些不同。

相似点是,它们本质都是一段操作的复用。在数学里,假如我们有一个函数:$f(x)=3x+9$,当我们计算 $f(3)$ 或者 $f(9)$ 的时候,都是在进行 $3x+9$ 这个操作。在程序中呢,也是类似的,我们用几行代码定义了一个函数,每次调用这个函数就是重复那几段代码的操作。

不同的是,在数学的函数中,我们只能定义 $f(x,y) = x^2 + xy + 3$ 这样的函数,却不能定义 $f(x,y) = x^2 + ny + m$ 这样的函数,我们不知道这里面的 $m$ 和 $n$ 是什么;我们定义的函数也不会影响到外部任何其他值或函数,假如我们在外部有一个值 $a = 3$,我们的函数 $f$ 是没有办法影响 $a$ 的值的。

而在计算机科学中,我们的函数可以变得很强大,它可能在函数体中使用了定义在函数外部的值,或者悄悄改变了外部的值。

这里只是关于函数在不同学科中简单异同点的介绍。本章,我们会讲解一些基本知识,比如如何定义函数,调用函数等等。在后续学习中,我们还会学到一些更高级的特性,希望你在学习过程中,能回头来思考一下,程序与数学中的函数的异同点。

本章包含:

  • 函数定义
  • 函数调用
  • 参数传递

函数定义

Python 中的函数,用关键字 def 来定义,它的语法大概长这个样子:

def 函数名(参数列表):
    函数体

这样看起来可能还是有点晦涩,让我们通过一个简单的例子来看看如何定义函数。

In [1]:
def max(a, b):
    if a > b:
        return a 
    else:
        return b

可以看到我们很简单的就定义了一个返回最大值的函数。注意代码中的 ( ):,它们都是半角符号。值得注意的是关键字 return,它用来设置这个函数的返回值,如果我们不写 return 的话,函数的返回值默认为 None

函数调用

我们其实已经在之前的课程中调用过函数了,就是我们在课程一开始使用的 print,我们现在来试试调用 max

In [2]:
print(max(3, 10))
10

是不是还挺简单的?接下来我们来看一点有意思的东西。

参数传递

下面是一段代码,先别着急运行它,猜测一下它会打印出什么结果。

In [3]:
a = 3

def foo(a):
    a = a + 1
    return a

foo(a)
print(a)
3

结果是 3,不知道你猜对了没?你可能也会误以为结果是 4,明明我们在 foo 这个函数里给 a 赋了一个新的值呀,为什么打印出来的 a 还是 3 呢?

其原因是,a 在传进函数后,其实是复制了一个新的 a 出来进行之后的操作,我们修改这个新的 a,并不会对函数外部的 a 造成影响。我们也可以简单的修改一下代码,达成另一种效果。

In [4]:
a = foo(a)
print(a)
4

我们可以看到这时候输出的结果就是 4 了。

现在让我们换一个例子,还是先别执行下面的代码哦,猜猜会打印出什么?

In [5]:
l = [1, 2, 3, 4]

def mutate(l):
    l[2] = 10
    return

mutate(l)
print(l[2])
10

结果是 10!是不是和你想象的不一样?明明我们之前说参数传进函数之后,会复制出一个新的来进行操作呀?

这其实和我们传进去的东西有关。在 Python 中,有些对象,是不可修改的,而有些是可修改的,这是什么意思呢?

像是 NumberString,它们都属于不可变的数据类型,每当它们被传进一个函数时,都是生成了一个它的复制品传进去。

而像 List,它属于可变数据类型,当我们向函数传递一个列表时,所传递的是其本身,当我们在函数内部对其进行修改后,外部的它,也会受到影响!

还记得我们一开始说的这句话吗?

我们的函数可以变得很强大,它可能在函数体中使用了定义在函数外部的值,或者悄悄改变了外部的值。

函数 mutate 就是一个例子,在写这样的函数时,一定要注意对外部值的修改,小心造成不可思议的事情...

现在,让我们结合之前学过的知识,试着计算一个列表中数字的和。

In [6]:
def sum(items):
    res = 0
    for item in items:
        res = res + item
    return res

print(sum(l))
17

匿名函数

接下来我们来介绍一下匿名函数,顾名思义,匿名函数,就是没有名字的函数。从语义上来讲,它其实和普通函数没有区别,只是不需要显式地定义函数名。

我们来随意定义一个匿名函数试试:

In [7]:
square = lambda x: x * x

print(square(2))
print(square(9))
4
81

在上面的代码中,我们定义了一个匿名函数,它取一个参数,并计算它的平方。你可以把 lambda x: x * x 看作是数学中的$f(x) = x^2$,而当你把它赋值给 square 并调用 square(2) 的时候,它实际上在运算 $f(2)$,于是便输出 4

其实,用 lambda 表达式定义函数,与下面这样定义完全没有区别:

In [8]:
def square(x):
    return x * x

有意思的是,在数学上,一个函数可以被看作一个变量。比如,当你定义 $f(x) = x + 1$,并声明 $g = f$,那么就有 $f(1) = g(1)$,$f(2) = g(2)$, ..., $f(n) = g(n)$。在编程时,同样也能如此。你可以把你定义好的函数赋值给变量。

In [9]:
another_square = square
print(another_square(2))
print(square(2))
print(another_square(9))
print(square(9))
4
4
81
81

高阶函数

我们目前定义的函数,都是需要数值作为参数,输出一个数值的。有时候,一个函数可以拿一个函数作为参数,或者输出一个函数。这样的函数就是高阶函数。

其实在数学上,我们已经见过高阶函数。$\frac{d}{dx}$ 就是一个高阶函数。它需要拿一个函数 $f$(比如 $f(x) = x^2$)作为参数,通过 $\frac{d}{dx}f$ 的操作,输出一个新的函数 $g$(即 $g(x) = 2x$)。计算机里的高阶函数也是如此。

可能你还没有完全理解,让我们来看一下例子:

In [10]:
# 定义一个平方函数
def square(x):
    return x * x

# 定义一个立方函数
def cubic(x):
    return x * x * x

# 定义一个高阶函数
def make_plus_one(f, x):
    return f(x) + 1

print(make_plus_one(square, 9))
print(make_plus_one(cubic, 3))
82
28

所以,这里的 make_plus_one,把第二个参数,塞到第一个参数(一个函数)里,输出数值作为结果。

我们也可以返回一个新的函数:

In [11]:
def make_plus_one_function(f):
    def new_function(x):
        return f(x) + 1
    return new_function

f = make_plus_one_function(square)
print(f(9))
82

你可能觉得有一点点绕,不过没关系,实在搞不懂的话可以先跳过。

案例

我们先来实现两个函数,第一个将列表里的每个数都乘二后 print,第二个将列表里的每个数都加上 3 后 print

我们先拿一个数组为例:

In [4]:
numbers = [3, 1, 4, 5]

def times2(items):
    for item in items:
        print(item * 2)

def plus3(items):
    for item in items:
        print(item + 3)

print("第一个函数:")
times2(numbers)
print("第二个函数:")
plus3(numbers)
第一个函数:
6
2
8
10
第二个函数:
6
4
7
8

有没有感觉它们实在是太像了!除了 * 2+ 3 不一样,其他地方完全没区别嘛!

这时,我们可以使用高阶函数!让我们改写一下之前的 times2plus3

In [6]:
def map(items, f):
    for item in items:
        print(f(item))


def times2_(nums): 
    map(nums, lambda x: x * 2)
    
def plus3_(nums): 
    map(nums, lambda x: x + 3)
    
print("第一个函数:")
times2_(numbers)
print("第二个函数:")
plus3_(numbers)
第一个函数:
6
2
8
10
第二个函数:
6
4
7
8

我们先定义了一个函数 map,它取一个列表与一个函数,并且对列表中的每个元素都应用它。之后,我们定义了全新版本的 times2plus3

在这里,map 就是一个高阶函数,看看 times2_plus3_ 的定义,有没有感觉很方便?