这篇笔记我们学习Python中函数的使用。
下面例子中我们定义了一个函数用来求向量的长度,函数接收2个float
类型参数,返回float
类型结果。
import math
def foo(x: float, y: float) -> float:
"""
计算向量长度
:param x: 向量x坐标
:param y: 向量y坐标
:return: 向量长度
"""
return math.sqrt(x ** 2 + y ** 2)
# 调用函数
result: float = foo(1, 1)
print(result)
代码中对于参数和返回值的类型我们使用了类型标注,它们是可以省略的,这里写类型标注的目的是为了提升代码可读性。
此外我们还对函数使用了DocString注释,这是一种特殊的注释,注释中以约定格式编写了参数和返回值的说明信息,这些注释可以被IDE识别解析,也可以被sphinx
等文档工具用来生成HTML格式的文档。Python中DocString注释有多种风格,以上使用的风格是reStructuredText(reST)。当然,DocString注释也是可选的,但是添加注释能让我们的代码可读性更好。
Python的函数参数支持默认值,如果有默认值的参数未被设置则会使用默认值。
def foo(a: int, b: int = 0):
print(f"foo({a}, {b})")
foo(1)
foo(1, 2)
可变参数可以传多个参数值,它们实际将以元组形式传入函数。
def foo(*nums: int):
print(nums)
foo(1)
foo(1, 2, 3)
Python中在调用函数时,实际上有位置参数和关键字参数两种传参方式,前面我们使用的都是位置参数,而按关键字传递参数的做法叫传递关键字参数,下面是一个例子。
def foo(message: str, loop: int):
for i in range(loop):
print(message)
foo(loop=3, message='Hello, world!')
代码中,foo()
函数接收两个参数,分别是message
和loop
,我们调用该函数时使用了关键字参数,它们的顺序和默认的位置参数是不同的。
注意:调用函数时如果混用位置参数和关键字参数,关键字参数必须放在位置参数之后。
可变关键字参数和可变参数类似,可变关键字参数使用两个星号**
定义,它常用于动态的函数参数扩展,下面是一个例子。
def insert_student(name, age, **kw):
print(f'name: {name}, age: {age}, kw: {kw}')
insert_student('Tom', 20, a=1, b=2)
代码中,传入的kw
参数实际上是字典类型,它包含两个元素,分别是键为a
的1
和键为b
的2
。
上面代码输出内容如下。
name: Tom, age: 20, kw: {'a': 1, 'b': 2}
命名关键字参数是一种特殊的关键字参数,它要求在调用时某些参数必须使用参数名来传递。合理使用命名关键字参数可以使函数调用传参更加清晰,避免位置参数的歧义。命名关键字参数通过在函数定义的参数列表中使用一个星号*
后跟参数名来实现。星号*
表示后面的参数必须使用关键字的形式来传递。
def foo(name, age, *, city, country):
print(f"{name} is {age} years old, lives in {city}, {country}.")
foo("Tom", 18, city="New York", country="USA")
上面代码中city
和country
是命名关键字参数,调用时必须显式地使用关键字传参,否则会报错。
前面我们介绍过,Python的函数可以返回多个值,它的本质上是一个元组,下面是一个例子。
def get_numbers() -> tuple[int, int, int]:
return 1, 2, 3
x, y, z = get_numbers()
函数返回多个值时,我们可以直接用逗号分隔的多个变量来接收返回值。
Python中函数本身也是一种对象,它可以被赋值到变量、存储到容器类型、作为函数参数以及作为函数返回值等,这类似于C语言的函数指针。下面代码我们将函数foo
本身赋值给了变量v
。
def foo(a: int, b: int) -> int:
print(f'{a} {b}')
return a + b
v = foo
如果你希望给v
加上类型标注,它实际上是这样的。
v: Callable[[int, int], int] = foo
如果一个函数接受一个或多个函数作为参数或者返回一个函数对象作为返回值,这个函数就被称为高阶函数。
下面例子代码我们将函数foo
作为参数传入了bar
函数中。
from typing import Callable
def foo(a: int, b: int) -> int:
print(f'{a} {b}')
return a + b
def bar(callback: Callable[[int, int], int]) -> int:
return callback(1, 2)
print(bar(foo))
代码中,bar()
函数就是一个高阶函数,它的参数callback
接收函数类型,我们用可选的类型标注限制了它的参数和返回值类型。
下面例子函数get_foo()
返回了一个函数对象foo
。
from typing import Callable
def foo(a: int, b: int) -> int:
print(f'{a} {b}')
return a + b
def get_foo() -> Callable[[int, int], int]:
return foo
get_foo()(1, 2)
代码中get_foo()
也是一个高阶函数,它返回了另一个函数,返回值的类型是Callable[[int, int], int]
。
Python中我们可以创建lambda表达式,它可以理解为一个没有名字的函数(匿名函数)。相较于普通函数,Python的lambda表达式通常用于实现简短的、一次性的功能,下面是一个例子。
from typing import Callable
def bar(callback: Callable[[int, int], int]) -> int:
return callback(1, 2)
result = bar(lambda a, b: a + b)
print(result)
代码中,bar()
函数是一个高阶函数,它接收一个函数作为参数,但我们传入的是一个匿名函数(lambda表达式),它简短的表达了一个相加的逻辑。
注意:
Python中的闭包(closure)是一个函数对象,它可以捕获并记住其定义时所在的环境变量。简单来说,闭包是在其定义的作用域之外执行时仍然能够访问该作用域内的变量的函数。闭包通常用于创建具有特定上下文或状态的函数。
创建一个闭包需要满足以下三个条件:
下面是一个闭包的例子。
def outer_function(x):
def inner_function(y):
return x + y
return inner_function
closure = outer_function(10)
result = closure(5)
print(result)
在这个例子中closure
是一个闭包对象,它“记住”了外部函数outer_function
中的变量x
的值为10
。当调用closure(5)
时,它会将y
的值设置为5
并计算x + y
的结果。上述代码的输出值为15
。