函数定义¶
本章我们将介绍编程语言中非常重要的一个概念——函数。这里的函数和我们之前在数学中所学习的函数,有一些相似点,也有些不同。
相似点是,它们本质都是一段操作的复用。在数学里,假如我们有一个函数:$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$ 的值的。
而在计算机科学中,我们的函数可以变得很强大,它可能在函数体中使用了定义在函数外部的值,或者悄悄改变了外部的值。
这里只是关于函数在不同学科中简单异同点的介绍。本章,我们会讲解一些基本知识,比如如何定义函数,调用函数等等。在后续学习中,我们还会学到一些更高级的特性,希望你在学习过程中,能回头来思考一下,程序与数学中的函数的异同点。
本章包含:
- 函数定义
- 函数调用
- 参数传递
def max(a, b):
if a > b:
return a
else:
return b
可以看到我们很简单的就定义了一个返回最大值的函数。注意代码中的 (
)
与 :
,它们都是半角符号。值得注意的是关键字 return
,它用来设置这个函数的返回值,如果我们不写 return
的话,函数的返回值默认为 None
。
函数调用¶
我们其实已经在之前的课程中调用过函数了,就是我们在课程一开始使用的 print
,我们现在来试试调用 max
。
print(max(3, 10))
是不是还挺简单的?接下来我们来看一点有意思的东西。
参数传递¶
下面是一段代码,先别着急运行它,猜测一下它会打印出什么结果。
a = 3
def foo(a):
a = a + 1
return a
foo(a)
print(a)
结果是 3
,不知道你猜对了没?你可能也会误以为结果是 4
,明明我们在 foo
这个函数里给 a
赋了一个新的值呀,为什么打印出来的 a
还是 3
呢?
其原因是,a
在传进函数后,其实是复制了一个新的 a
出来进行之后的操作,我们修改这个新的 a
,并不会对函数外部的 a
造成影响。我们也可以简单的修改一下代码,达成另一种效果。
a = foo(a)
print(a)
我们可以看到这时候输出的结果就是 4
了。
现在让我们换一个例子,还是先别执行下面的代码哦,猜猜会打印出什么?
l = [1, 2, 3, 4]
def mutate(l):
l[2] = 10
return
mutate(l)
print(l[2])
结果是 10
!是不是和你想象的不一样?明明我们之前说参数传进函数之后,会复制出一个新的来进行操作呀?
这其实和我们传进去的东西有关。在 Python 中,有些对象,是不可修改的,而有些是可修改的,这是什么意思呢?
像是 Number
和 String
,它们都属于不可变的数据类型,每当它们被传进一个函数时,都是生成了一个它的复制品传进去。
而像 List
,它属于可变数据类型,当我们向函数传递一个列表时,所传递的是其本身,当我们在函数内部对其进行修改后,外部的它,也会受到影响!
还记得我们一开始说的这句话吗?
我们的函数可以变得很强大,它可能在函数体中使用了定义在函数外部的值,或者悄悄改变了外部的值。
函数 mutate
就是一个例子,在写这样的函数时,一定要注意对外部值的修改,小心造成不可思议的事情...
现在,让我们结合之前学过的知识,试着计算一个列表中数字的和。
def sum(items):
res = 0
for item in items:
res = res + item
return res
print(sum(l))
square = lambda x: x * x
print(square(2))
print(square(9))
在上面的代码中,我们定义了一个匿名函数,它取一个参数,并计算它的平方。你可以把 lambda x: x * x
看作是数学中的$f(x) = x^2$,而当你把它赋值给 square
并调用 square(2)
的时候,它实际上在运算 $f(2)$,于是便输出 4
。
其实,用 lambda
表达式定义函数,与下面这样定义完全没有区别:
def square(x):
return x * x
有意思的是,在数学上,一个函数可以被看作一个变量。比如,当你定义 $f(x) = x + 1$,并声明 $g = f$,那么就有 $f(1) = g(1)$,$f(2) = g(2)$, ..., $f(n) = g(n)$。在编程时,同样也能如此。你可以把你定义好的函数赋值给变量。
another_square = square
print(another_square(2))
print(square(2))
print(another_square(9))
print(square(9))
高阶函数¶
我们目前定义的函数,都是需要数值作为参数,输出一个数值的。有时候,一个函数可以拿一个函数作为参数,或者输出一个函数。这样的函数就是高阶函数。
其实在数学上,我们已经见过高阶函数。$\frac{d}{dx}$ 就是一个高阶函数。它需要拿一个函数 $f$(比如 $f(x) = x^2$)作为参数,通过 $\frac{d}{dx}f$ 的操作,输出一个新的函数 $g$(即 $g(x) = 2x$)。计算机里的高阶函数也是如此。
可能你还没有完全理解,让我们来看一下例子:
# 定义一个平方函数
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))
所以,这里的 make_plus_one
,把第二个参数,塞到第一个参数(一个函数)里,输出数值作为结果。
我们也可以返回一个新的函数:
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))
你可能觉得有一点点绕,不过没关系,实在搞不懂的话可以先跳过。
案例¶
我们先来实现两个函数,第一个将列表里的每个数都乘二后 print
,第二个将列表里的每个数都加上 3 后 print
。
我们先拿一个数组为例:
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)
有没有感觉它们实在是太像了!除了 * 2
和 + 3
不一样,其他地方完全没区别嘛!
这时,我们可以使用高阶函数!让我们改写一下之前的 times2
与 plus3
。
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)
我们先定义了一个函数 map
,它取一个列表与一个函数,并且对列表中的每个元素都应用它。之后,我们定义了全新版本的 times2
与 plus3
。
在这里,map
就是一个高阶函数,看看 times2_
与 plus3_
的定义,有没有感觉很方便?