函数基础
注:
在本资源各处会有一些阅读理解练习。这些题目旨在帮助读者活学活用文中的知识点。练习题的答案可以在本页底部找到。
定义函数允许我们包装代码片段并描述进入和离开其中的信息。你可以在各种情况下重复使用这个“代码胶囊”。比如说,假设你想要数一个字符串中有多少元音,你可以定义以下的函数来达到该目的:
def count_vowels(in_string): """ 返回 `in_string` 中有多少元音 """ num_vowels = 0 vowels = "aeiouAEIOU" for char in in_string: if char in vowels: num_vowels += 1 # 等值于 num_vowels = num_vowels + 1 return num_vowels
执行此代码会定义函数 count_vowels
。这个函数期待一个对象作为输入参数(input argument),由 in_string
代名,并会返回(return)该对象中元音的数量。使用 count_vowels
并向其输入一个对象的过程叫做调用(call)该函数:
>>> count_vowels("Hi my name is Ryan") 5
最棒的一点在于你可以重复使用这个函数!
>>> count_vowels("Apple") 2 >>> count_vowels("envelope") 4
在本节中,我们将会了解定义和调用Python函数的语法。
定义:
Python函数(function)是一个包装代码的对象。调用(call)函数将会执行包装的代码并返回(return)一个对象。你可以定义函数使其接受参数(argument),也就是输入进包装代码的对象。
def
语句
类似于 if
,else
,和 for
,Python保留了 def
语句来表达函数(以及我们会在之后讨论的其它几个构造体)的定义。以下是Python函数定义的一般形式:
def <function name>(<function signature>): """ 说明字符串 """ <encapsulated code> return <object>
<function name>
是任何合法的变量名,在这之后必须跟随一对括号和一个冒号。<function signature>
描述函数的输入参数。如果函数不接受任何变量,你可以留白这一部分(你仍然需要提供括号,但是仅仅括号并不会包装任何参数)。说明字符串(documentation string)(一般叫做“docstring”)可以长达多行并解释函数的目的。它是可选的。
<encapsulated code>
包含正常的Python代码,其由相对于def
语句的缩进限定。return
当函数中包装的代码遇到这个标示后,函数会返回标示后跟随的对象并立刻终止函数的执行。
Python同样保留了 return
语句来表达函数的终止;如果遇到了 return
,那程序将立即终止函数的执行并返回 return
右边的对象。
请注意,与if语句和for循环一样,def
语句必须由冒号结尾,且函数的主体代码必须由空格限定:
# 错误的缩进 def bad_func1(): x = 1 return x + 2
# 错误的缩进 def bad_func2(): x = 1 return x + 2
# 漏了冒号 def bad_func3() x = 1 return x + 2
# 漏了括号 def bad_func4: x = 1 return x + 2
# 这个没问题 def ok_func(): x = 1 return x + 2
阅读理解:编写简单函数
编写一个名为 count_even
的函数。它应该接受一个名为 numbers
的整数可迭代物作为参数。该函数应该返回列表中偶数的数量。注意包含一个合理的docstring。
return
语句
一般来讲,return
语句可以返回任何Python对象。同时,你可以使用一个空的 return
语句或在函数中完全不使用返回(return)语句。在这两种情况下,函数都会返回 ``None`` 对象。
# 本函数返回 `None` # 一个“空”的返回语句 def f(): x = 1 return
# 本函数返回 `None` # 完全没有用返回语句 def f(): x = 1
所有Python函数都会返回某个对象。就算是内置的 print
函数在打印后也会返回 None
!
# `print` 函数返回 `None` >>> x = print("hi") hi >>> x is None True
警告!
请注意不要错误地漏掉或使用空的返回语句。你将依然能够调用你的函数,但是它在任何情况下都会返回 None
!
函数并不一定要有除了返回语句之外的代码。比如说,我们可以使用 sum
和生成器理解(见本模组前一节)来简化我们的 count_vowels
函数:
# 你可以直接描述函数返回的对象 def count_vowels(in_string): """ 返回 `in_string` 中元音的数量 """ return sum(1 for char in in_string if char in "aeiouAEIOU")
多个 return
语句
你可以在一个函数内使用不止一个 return
语句。在处理边缘情况或优化代码时这可能会有用。假设你想要你的函数通过泰勒级数(Taylor series)来模糊计算 \(e^{x}\),当 \(x = 0\) 时函数应该立刻返回 1.0
:
def compute_exp(x): """ 使用泰勒级数来计算 e^x """ if x == 0: return 1.0 from math import factorial return sum(x**n / factorial(n) for n in range(100))
如果 x==0
为 True
,那么程序将执行第一个 return
语句,返回 1.0
并立刻“终止”该函数,且不执行之后的代码。
如上所述,就算下方有额外的代码,return
语句也会导致函数立刻终止且不执行之后的代码。在一次函数调用中,程序不可能遇到多个 ``return`` 语句。因此,如果你想要返回多个对象,那么你的函数必须返回单个包含这些项目的容器,如列表会元组。
# 函数返回多个项目 def bad_f(x): # 译者注:函数名字大意为:不好的函数 """ 返回 x**2 和 x**3""" return x**2 # 此代码永远都不会被执行! return x**3 def good_f(x): # 译者注:函数名字大意为:好的函数 """ return x**2 and x**3""" return (x**2, x**3)
>>> bad_f(2) 4 >>> good_f(2) (4, 8)
单行函数
你可以在一行内定义只有一个返回语句的函数:
def add_2(x): return x + 2
可以被重写为:
def add_2(x): return x + 2
尽量只在函数极其简单到不需要docstring就能理解时使用此功能。切记不要滥用。
参数
你可以在函数签名(function signature)部分通过一序列由逗号隔开的变量名来描述函数的位置参数(positional argument)。比如说,以下代码为函数 is_bounded
提供了 x
,lower
,和 upper
三个参数:
def is_bounded(x, lower, upper): return lower <= x <= upper
你可以用几种不同的方法来向函数提供参数:
通过位置提供参数
输入到 is_bounded
中的对象会根据它们的位置来赋值函数的输入变量。也就是说,is_bounded(3, 2, 4)
会赋值 x=3
,lower=2
,和 upper=4
,其根据函数输入参数的位置顺序来对应:
# 计算:2 <= 3 <= 4 # 通过位置来提供输入 >>> is_bounded(3, 2, 4) True
向函数输入太少或太多参数会导致 TypeError
。
# 输入太少:报错 is_bounded(3) # 输入太多:报错 is_bounded(1, 2, 3, 4)
通过关键词提供参数
在顺序不重要的时候,你也可以在给函数输入参数时提供它们的关键词(也就是名字)来对应对象和参数。这会帮助你编写易读和灵活的代码:
# 计算:2 <= 3 <= 4 # 通过参数名字来提供输入对象 >>> is_bounded(lower=2, x=3, upper=4) True
你可以混合关键词参数和位置参数,但位置参数应该在前:
# 计算:2 <= 3 <= 4 # `x` 是位置参数 # `lower` 和 `upper` 关键词参数 >>> is_bounded(3, upper=4, lower=2) True
请注意,如果你提供了一个关键词参数,那么它之后所有的参数都应该是关键词参数:
# 你不可以在关键词参数后使用位置参数 >>> is_bounded(3, lower=2, 4) SyntaxError: positional argument follows keyword argument
有默认值的参数
你可以提供参数的默认值。如果用户在调用函数时没有提供此参数,那么函数将会使用定义的默认值。请回忆我们的 count_vowels
函数。假设我们想要提供将“y”算为一个元音的选择。因为我们知道人们一般不会将“y”视为元音,所以我们可以默认将“y”除外:
def count_vowels(in_string, include_y=False): """ 返回 `in_string` 中元音的数量""" vowels = "aeiouAEIOU" if include_y: vowels += "yY" # 将 "y" 加到元音列表中 return sum(1 for char in in_string if char in vowels)
现在,如果在调用 count_vowels
时只提供了 in_string
,那么 include_y
会使用默认值 False
:
# 使用默认值:不将y算为元音 >>> count_vowels("Happy") 1
我们可以提供默认值之外的输入:
# 不使用不认值:将y算为元音 >>> count_vowels("Happy", True) 2 # 你依然可以通过名字来描述输入参数 >>> count_vowels(include_y=True, in_string="Happy") 2
在函数签名中,有默认值的输入参数必须在所有位置参数之后:
# 这没问题 def f(x, y, z, count=1, upper=2): return None
# 这会导致语法错误 def f(x, y, count=1, upper=2, z): return None
阅读理解:函数和参数
编写一个函数 max_or_min
。它接受两个位置参数 x
和 y
(将会被赋值为数字)以及一个 mode
变量,其默认值为 "max"
。
此函数应该根据 mode
返回 min(x, y)
或 max(x, y)
。如果 mode
既不是 "max"
也不是 "min"
的时候让函数返回 None
。
包含一个描述性的docstring。
支持任意多的位置参数
Python提供了定义可以接受任意多位置参数的函数的语法。使用 def f(*<var_name>)
语法来定义这类输入。
# * 符号表明调用 `f` 时可以向 `args` # 输入任意多的参数。 def f(*args): # 所有向 `f` 输入的参数都会被“打包”成一个元组 # 并赋值给变量 `args`。 # `f()` 会赋值 `args = tuple()` # `f(x, y, ...)` 会赋值 `args = (x, y, ...)` return args
因为Python不能提前知道 f
会收到多少个参数,因此它所有的输入参数都会被打包成一个元组并赋值给变量 args
:
# 向 `f` 输入0个参数 >>> f() () # 向 `f` 输入1个参数 >>> f(1) (1,) # 向 `f` 输入3个参数 >>> f((0, 1), True, "cow") ((0, 1), True, "cow")
你可以将此语法与位置参数和默认参数混合使用。任何在被打包的变量后的变量必须提供参数名:
def f(x, *seq, y): print("x is: ", x) print("seq is: ", seq) print("y is: ", y) return None
>>> f(1, 2, 3, 4, y=5) # 必须提供 `y` 的名字
x 是: 1 seq 是: (2, 3, 4) y 是: 5
>>> f("cat", y="dog") # 没有输入任何额外的位置参数
x 是: "cat" seq 是: () y 是: "dog"
阅读理解:任意多的参数
编写一个名为 mean
的函数,其接受任意多的数字参数并计算所有数字的平均值。如,mean(1, 2, 3)
应返回 \(\frac{1 + 2 + 3}{3} = 2.0\)
如果没有收到任何输入,函数应返回 0.
。记得测试你的函数并编写一个docstring。
如我们所见,在函数定义的签名中使用 *
意味着将任意多的参数打包成一个元组。同时,在调用函数时 *
也意味着解包可迭代物并将其作为位置参数输入到函数中:
# 在调用函数时使用 `*` 来解包可迭代物,并将其 # 成员作为位置参数输入到函数中 def f(x, y, z): return x + y + z >>> f(1, 2, 3) 6 # `*` 意味着:解包 `[1, 2, 3]` 的内容并将每个 # 物件分别输入为x,y,和z >>> f(*[1, 2, 3]) # 等值于:f(1, 2, 3) 6
在以下范例中,我们使用 *
来:
定义一个接受任意多参数并将其打包为元组的函数
通过解包可迭代物来调用函数并输入任意多的参数
def number_of_args(*args): return len(args)
>>> number_of_args(None, None, None, None) 4 >>> some_list = [1, 2, 3, 4, 5] # 将列表本身作为唯一的参数输入 >>> number_of_args(some_list) 1 # 将列表的5个成员解包并作为多个参数输入进函数 >>> number_of_args(*some_list) 5
支持任意多的关键词参数
我们可以使用 def f(**<var_name>)
语法来定义一个接受任意多的关键词(keyword)参数的函数。
注意单个星号 *
用来代表任意多的位置参数,而 **
代表着任意多的关键词参数。
# ** 符号意味着在调用 `f` 时可以向 `args` 输入 # 任意多的关键词参数。 def f(**args): # 所有输入到 `f` 的关键词参数都会被“打包”成一个词典 # 并赋值到变量 `args` 中 # `f()` 会赋值 `args = {}`(一个空词典) # `f(x=1, y=2, ...)` 会赋值 `args = {"x":1, "y":2, ...}` return args
因为Python无法预先知道 f
会收到多少个关键词参数,所以所有收到的关键词参数都会被打包成一个词典(dictionary)。词典允许你通过关键词(将其转化成字符串)查询并设置关键词对应的值。这个词典被赋值到变量 args
上。我们会在后面一节专门讨论词典。
>>> f() # 向 `f` 输入0个参数 {} >>> f(x=1) # 向 `f` 输入1个参数 {'x': 1} >>> f(x=(0, 1), val=True, moo="cow") # 向 `f` 输入3个参数 {'moo': 'cow', 'val': True, 'x': (0, 1)}
这一语法可以和位置参数和默认参数混合使用。在函数定义签名中,**
后不能有任何额外的参数:
def f(x, y=2, **kwargs): print("x 是:", x) print("y 是:", y) print("kwargs 是:", kwargs) return None # 译者注:kwargs是keyword arguments的缩写,也就是关键词参数。 # 译者注:此命名方式是传统,所以请尽量遵循
# 向 `f` 输入任意的关键词参数 >>> f(1, y=9, z=3, k="hi")
x 是: 1 y 是: 9 kwargs 是: {'z': 3, 'k': 'hi'}
# 没有输入任何额外的关键词参数 >>> f("cat", y="dog")
x is: cat y is: dog kwargs is: {}
以下函数接受任意多的位置参数和任意多的关键词参数:
# 接受任意多的位置和关键词参数 def f(*x, **y): # 所有位置参数都打包成元组 `x` # 所有关键词参数都打包成词典 `y` print(x) print(y) return None >>> f(1, 2, 3, hi=-1, bye=-2, sigh=-3)
(1, 2, 3) {'hi': -1, 'bye': -2, 'sigh': -3}
如上所见,在函数定义的签名中 **
意味着将任意多的关键词参数打包成一个词典。同时,在调用函数时 **
意味着解包词典并将其的键值对(key-value pair)作为函数关键词参数输入:
# 在调用函数时使用 `**` 来解包词典并将其成员作为 # 关键词参数输入到函数中 def f(x, y, z): return 0*x + 1*y + 2*z >>> f(z=10, x=9, y=1) 21 >>> args = {"x": 9, "y": 1, "z": 10} >>> f(**args) # 等值于:f(x=9, y=1, z=10) 21
在以下范例中,我们用 **
来:
定义一个接受任意多关键词参数的函数并将其打包成词典。
调用函数并通过解包词典向其输入任意多的关键词参数。
def print_kwargs(**args): print(args)
>>> print_kwargs(a=1, b=2, c=3, d=4) {'a': 1, 'b': 2, 'c': 3, 'd': 4} >>> some_dict = {"hi":1, "bye":2} # 解包词典的键值对并将其作为关键词参数输入到函数中 >>> print_kwargs(a=2, umbrella=True, **some_dict) {'a': 2, 'umbrella': True, 'hi': 1, 'bye': 2}
函数也是对象
在定义之后,函数和任何其它Python对象,如列表或字符串或整数,的行为差不多。你可以将函数赋值到变量上:
>>> var = count_vowels # `var` 现在引用函数 `count_vowels` >>> var("Hello") # 你现在可以“调用” `var` 2
你可以将函数存储到一个列表中:
my_list = [count_vowels, print] for func in my_list: func("hello") # 迭代0:调用 `count_vowels("hello")` # 迭代1:调用 `print("hello")`
你也可以在代码任何地方调用函数,且它的返回值会在原地返回:
if count_vowels("pillow") > 1: print("that's a lot of vowels!")
当然,这在列表理解表达式中也成立:
>>> sum(count_vowels(word, include_y=True) for word in ["hi", "bye", "guy", "sigh"]) 6
“打印”一个函数并不会揭露些什么。这仅仅打印此函数对象在内存中的地址:
>>> print(count_vowels) <function count_vowels at 0x000002A32898C6A8>
官方说明文档链接
阅读理解答案
编写简单函数:解
def count_even(numbers): """ 返回可迭代物中偶数的数量 """ total_even = 0 for num in numbers: if num % 2 == 0: total += 1 return total
或通过使用生成器理解:
def count_even(numbers): """ 返回可迭代物中偶数的数量 """ return sum(1 for num in numbers if num % 2 == 0)
函数和参数:解
def max_or_min(x, y, mode="max"): """ 根据 `mode` 参数返回 `max(x,y)` 或 `min(x,y)`。 Parameters ---------- x : Number y : Number mode : str 'max' 或 'min' Returns ------- 两值的最大或最小值。如果mode不合法那么会返回 `None`。""" if mode == "max": return max(x, y) elif mode == "min": return min(x, y) else: return None
请注意你其实可以在 mode
输入不正确时让你的函数报错(raise an “exception”)。事实上,这才应该是这种情况下更加合理的函数行为。
这种解决方案如下:
def max_or_min(x, y, mode="max"): if mode == "max": return max(x, y) elif mode == "min": return min(x, y) else: raise Exception("`mode` was passed an invalid value: {}".format(mode))
任意多的参数:解
def mean(*seq): """ 返回函数参数的平均值 """ if len(seq) == 0: return 0 total = 0 for num in seq: total += num return total / len(seq)
或者我们可以利用以下两点来做一些骚操作:
当
seq
为空时bool(seq)
是False
的事实单行if-else语法
def mean(*seq): """ 返回函数参数的平均值 """ return sum(seq) / len(seq) if seq else 0
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论