函数
函数是封装好以供调用的一段Python代码。Python使用关键字def定义一个函数,例如定义一个加法函数并调用的代码如下:
def cal_add(a, b):
result = a + b
print(f"{a} + {b} = {result}")
return result
cal_add(1, 2)1 + 2 = 3可见,Python不需要开发者在编写函数时详细地给出函数的返回类型与形参类型。当然,聪明的你完全可以给出这些类型,请参见类型提示章节。
函数的功能
函数需要执行,实现一些功能,有的函数执行一些操作,有的函数运算后返回一些数据。例如,我们刚刚定义的加法函数会计算两个传入的值的和,然后将其打印出来,再返回结果。
调用函数
def cal_add(a, b):
result = a + b
print(f"{a} + {b} = {result}")
return result
cal_add(1, 2)
cal_add(a=1, b=2)
cal_add(b=1, a=1)上面的三种对cal_add函数的调用是等价的。如果不使用关键字方式(keyword,即xxx=yyy的传参方式)传参,则默认按照顺序接收参数。
你也可以混合使用cal_add(1, b=2)的方式调用函数,此时关键字传参必须位于顺序传参之后,即b=2必须位于1之后。
你不可以使用cal_add(2, a=1)的方式调用函数,否则会得到TypeError,显示函数接收到了多个参数a,同时没有接收到参数b。因此,建议避免混合使用传参方式,并在有必要时(如需要跳过某个有默认值的参数)完全使用关键字传参。
只需要在函数签名中简单设置,即可让参数拥有默认值。调用函数时如果没有接收到有默认值的参数,则会使用默认值。
def cal_add(a, b=2):
result = a + b
print(f"{a} + {b} = {result}")
return result
cal_add(1, 2)
cal_add(1)1 + 2 = 3
1 + 2 = 3如果需要进行类型提示,则可以写为def cal_add(a: int, b: int = 2)。更多类型提示请参见这里。
返回值
在函数体中,return关键字用于返回。函数可以不返回任何值,不返回形同于返回None。
在Python中,以下三种函数写法是完全一致的,它们都没有返回值,或者说都返回None。
def function1():
print("Meow!")
def function2():
print("Meow!")
return
def function3():
print("Meow!")
return None函数可以返回任意类型的对象,包括前面介绍的基本类型和你的自定义类型。你可以在调用函数时接收函数的返回值(如有),例如:
def cal_add(a, b):
"""
Function to add two numbers.
:param a: First number
:param b: Second number
:return: Sum of a and b
"""
return a + b
a = 10
b = 20
c = cal_add(a, b)
print(f"{a} + {b} = {c}")你也完全可以不使用中间变量c接收函数的返回值,直接使用print(f"{a} + {b} = {cal_add(a, b)}"),也是完全合法的。
docstring
上面的函数中,我们使用"""..."""的方式写了一种很新的注释。这里的注释对程序的运行没有任何影响,单纯是为辅助编码而存在的。
这种使用三重双引号包裹起来的长注释称为docstring,即文档。Python中任何封装的代码都可以拥有docstring,例如<module>.py文件、cal_add()函数与Cat类型,都可以通过在其中的头部(函数和类则在内部首行)提供docstring来描述其行为。
在Python的层面上,你可以获取封装好的对象的docstring:
def cal_add(a, b):
"""
Function to add two numbers.
:param a: First number
:param b: Second number
:return: Sum of a and b
"""
return a + b
a = 10
b = 20
c = cal_add(a, b)
print(f"{a} + {b} = {c}")
print(cal_add.__doc__)输出如下。
10 + 20 = 30
Function to add two numbers.
:param a: First number
:param b: Second number
:return: Sum of a and bdocstring的本质是特殊的注释,因此不会影响程序的运行。
可变参数
*args
def cal_sum(*args):
return sum(args)
print(cal_sum(1, 2, 3, 4, 5))15这段代码可能看起来很困惑,实际工作中没人会这么写,这里仅用作介绍*args。
*args正是arguments,参数,这里表示函数cal_sum可以传入任意数量的参数。我们可以在函数cal_sum中print(args),会得到(1, 2, 3, 4, 5)这个元组。
你可以把*args换成*brgs、*any以及任何你乐意的名字,真正起作用的是*星号,在函数参数中的*会将超出其之前的、函数定义中的确定参数数量的传参打包成元组,存储到名为args、brgs或any的元组中。在实际中,我们约定俗成将其写作*args。
*args必须位于其他普通参数之后,且并非必须提供。
**kwargs
def cal_sum(a, b, *args, **kwargs):
print(a)
print(b)
print(args)
print(kwargs)
return sum(args)
cal_sum(1, 2, 3, 4, 5, x=1, y=2)1
2
(3, 4, 5)
{'x': 1, 'y': 2}*args会将接收到的任意数量的参数打包为元组arg,而**kwargs会将接收到的任意数量的关键字参数打包为字典kwargs。**kwargs也并非必须提供,其命名也是约定俗成的写法。
在一般情况下不建议滥用*args和**kwargs,而是建议将函数的签名填写完整清晰。
函数重载
Python不支持传统意义上的函数重载。
在C++、Java等语言中,重复定义相同名称、不同签名的函数即所谓的函数重载。调用函数时,将由语言来决定使用哪种函数实现。但Python不同,你只能给出一种函数的实现;如需重载,只能为这个函数提供不同的签名。
from typing import overload
@overload
def cal_add(a: int, b: int) -> int: ...
@overload
def cal_add(a: float, b: float) -> float: ...
@overload
def cal_add(a: str, b: str) -> str: ...
def cal_add(a, b):
result = a + b
print(f"{a} + {b} = {result}")
return result
cal_add(1, 2)
cal_add(1.5, 2.5)
cal_add("1", "2")你瞧,与其称之为函数重载,不如将其视作一种新型的类型提示。overload是装饰器,代码4-9行重复给出了三次cal_add函数的签名和返回值,但不给出具体的函数实现,而是在给出所有的重载后再仅提供一种实现。
在上面的例子中,由于Python中的+运算符能够处理整形、浮点型和字符串之间的加法运算,因此我们没有写出重载最常见的模样。事实上,Python中常见的重载更类似下面的模样:
from typing import Any, overload
@overload
def create_std() -> dict[str, Any]: ...
@overload
def create_std(obj: dict[str, Any]) -> dict[str, Any]: ...
@overload
def create_std(name: str, age: int) -> dict[str, Any]: ...
def create_std(obj: dict[str, Any] = None, name: str = None, age: int = None):
if obj is not None:
new_std = obj.copy()
return new_std
elif name is not None and age is not None:
return {
"name": name,
"age": age
}
else:
return {
"name": "default_name",
"age": 18
}注意到,overload确实只有类型提示的作用,而我们需要在唯一的create_std的实现中完成所有重载的条件分支与实现。上面只是给出示例以方便理解,实际工程中的重载方式与条件判断可能还有很大区别。
装饰器是什么?
位于函数签名的上一行、以@开头的东西就是装饰器,将在后面的教程中介绍。


MangoFanFan_