函数概述
函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。是可以向调用者返回某个值(至少是 None)的一组语句。
函数使代码的组织模块化,提供了代码的利用率。例如 print() 函数实现打印功能,input() 函数实现输入功能,需要它们的地方,都可以使用。
函数通过函数名称来使用它,传入零个或多个参数,并在函数体执行中被使用。
# 函数 abs() 返回一个数的绝对值
a = abs(-3.14)
a
3.14
# print() 函数打印传入的参数值,返回 None
n = print('hello, world')
n is None
hello, world
True
按照上述定义,类也被称作函数(例如内置函数 int,list 等)。
我们通常所说的函数,是指使用 def 语句和 lambda 表达式定义的函数。
而在类内部定义,使用属性表示法来调用的函数,我们习惯称作方法。
# int 类实例化返回整数对象
int('1')
1
True
# list 类的方法 append 将一个对象加入列表中,返回值为 None
a = []
b = list.append(a,'123')
a, b is None
(['123'], True)
函数有内置函数和内置方法,用户也可以自定义函数和方法。定义了一个函数,即创建了一个函数对象,可以通过函数名和必要的参数调用它。
def my_sum(lst):
'将列表中的字符串数字转为数字求和' # 函数文档说明
rlt = sum(map(float,lst)) # 调用内置函数求值
return rlt # 返回结果
my_sum(['1','3.14',2]) # 调用自定义函数
6.140000000000001
函数定义
函数定义有两种方式,def 语句定义有名字的函数(详见 def 定义函数),lambda 表达式定义匿名函数(详见 lambda 函数)。
定义一个函数,即是创建了一个函数可执行代码的包装器,他将函数想要实现的功能包装起来。然后通过调用它来实现其功能。def 语句 详细介绍了定义函数的语法规则,下面看看如何将一个功能包装起来。
例如下列函数,实现了将列表中的 字符串整数 以及 整数 相加求和的功能,以后只要遇到这种情况,都可以用它来求和:
6
# def 定义
def sumpro(lst):
return sum(int(i) for i in lst)
6
# lambda 表达式定义
lambda lst: sum(int(i) for i in lst)
<function __main__.<lambda>(lst)>
lst = [['2','8'],['3','4']]
sorted(lst,key=lambda lst: sum(int(i) for i in lst))
[['3', '4'], ['2', '8']]
函数定义,也可以定义为实现功能,但没有返回值(默认返回 None)的过程。
例如下列函数,实现了将列表中的字符串整数都转换为整数:
def convert_to_numb(lst):
for i in range(len(lst)):
if type(lst[i]) != int:
lst[i] = int(lst[i])
lst = ['1',2,'3']
n = convert_to_numb(lst)
lst, n is None
([1, 2, 3], True)
函数定义所使用的函数名称,不能与当前作用域中以定义的名称相同,这会屏蔽掉已存在的名称,或将自定义的函数对象重新赋值给了该名称。
'123'
def str(x):
return f'x={x}'
str(123) # 屏蔽掉了内置名称 str
'x=123'
f = 0
def f():
pass
f # f 被重新赋值
<function __main__.f()>
函数形参
形参是函数定义中指定的参数名称。指定某个参数的形式,决定了该形参在函数调用时,可以接受实参的方式。关于实参详见 函数调用。
因而形参分为五种:
- 位置或关键字:指定一个可以作为 位置参数 传入也可以作为 关键字参数 传入的实参。这是默认的形参类型,但有默认值的形参必须置于无默认值的形参之后。
def f(a,b=None):
print(f'a={a},b={b}')
# 位置实参传入
f(1,2)
# 关键字实参传入
f(b=2,a=1)
a=1,b=2
a=1,b=2
- 仅限位置:指定一个只能通过位置传入的参数。仅限位置形参通过在函数定义的形参之后包含一个
/
字符来定义。/
之前的参数为仅限位置形参,之后的形参为默认形参类型。有默认值的形参也必须置于无默认值的形参之后。
def f(a,b=None,/,c=None): # 因为 b 有默认值,c 必须要有默认值
print(f'a={a},b={b},c={c}')
# 按位置传参调用
f(1,2,c=3)
# 关键字传参则不允许
f(a=1,b=2)
a=1,b=2,c=3
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-6-52b3afdaad4c> in <module>
4 f(1,2,c=3)
5 # 关键字传参则不允许
----> 6 f(a=1,b=2)
TypeError: f() got some positional-only arguments passed as keyword arguments: 'a, b'
- 仅限关键字:指定一个只能通过关键字传入的参数。仅限关键字形参可通过在函数定义的形参中包含单个 可变位置形参 或者在形参之前放一个
*
来定义。可变位置形参 或 *
之后的参数为仅限关键字形参。
def f(*a,b=None,c=None): # b 和 c 必须有默认值
print(f'a={a},b={b},c={c}')
# 位置传参将被解读为可变位置参数
f(1,2,3)
# 关键字传参
f(1,b=2,c=3)
a=(1, 2, 3),b=None,c=None
a=(1,),b=2,c=3
def f(*,a,b=None,c):
print(f'a={a},b={b},c={c}')
# 关键字传参
f(b=2,a=1,c=3)
# 位置传参不允许
f(1,2,3)
a=1,b=2,c=3
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-1-9d584fa28622> in <module>
4 f(b=2,a=1,c=3)
5 # 位置传参不允许
----> 6 f(1,2,3)
TypeError: f() takes 0 positional arguments but 3 were given
- 可变位置:指定一个可以接受任意数量的位置参数传入的参数。这种形参可通过在形参名称前加缀
*
来定义,并将接受到的参数封装成一个元组。该参数如果接受到了实参,它前面的参数必须为仅限位置参数。
def f(a,b=None,*c):
print(f'a={a},b={b},c={c}')
# c 没有接受参数
f(1); f(b=2,a=1)
# c 接受到了参数
f(1,2,3,4,5)
a=1,b=None,c=()
a=1,b=2,c=()
a=1,b=2,c=(3, 4, 5)
- 可变关键字:指定一个可以接受任意数量的关键字参数的参数。这种形参可通过在形参名称前加缀
**
来定义,并将接受到的参数封装成一个字典。
def f(a,b=None,**c):
print(f'a={a},b={b},c={c}')
f(1,2,c=3,d=4)
f(d=4,b=2,a=1,c=3)
a=1,b=2,c={'c': 3, 'd': 4}
a=1,b=2,c={'d': 4, 'c': 3}
带默认值的参数,可变位置参数和可变关键字参数,调用函数时可以不传参。
def f(a=1,*b,c=3,**d):
print(f'a={a},b={b},c={c},d={d}')
f()
a=1,b=(),c=3,d={}
默认值只会执行一次,这条规则很重要。如果参数有默认值且为可变对象,则需要做必要的限制:
def f(a=[]):
print(id(a))
a.append(1)
print(a)
f() # 多次调用会引用参数指向的同一个对象
f()
2399568560064
[1]
2399568560064
[1, 1]
# 可以拷贝一个副本
def f(a=[]):
b = a.copy()
b.append(1)
print(b)
f()
f()
[1]
[1]
# 或者修改参数
def f(a=None):
if a == None:
a = []
a.append(1)
else:
a.append(1)
print(a)
f()
f([])
[1]
[1]
函数的形参可以使用标注,标注的语法是参数后面接一个冒号 :
,然后接一个表达式(任意表达式),通常用来指明应该(不是必须)传递什么类型的参数等。标注提高了代码的可读性:
# a 标注为字符串类型,b 标注为整数,并设置默认值 2
def f(a:str,b:int=2):
return a*b
f('Hi',3)
'HiHiHi'
def f(a:'字符串',b:'整数'=2):
return a*b
f('Hi')
'HiHi'
函数返回值
函数返回值通过 return 语句 来实现,调用函数时,返回 return 语句之后表达式的值,没有 return 语句或 return 语句之后为空的函数,调用函数默认返回 None。
def f(x):
return x**2
f(2)
4
def f():
pass
f() is None
True
函数执行到 return 语句,则结束当前函数的调用,可以通过条件判断,返回特定结果:
def f(x=None):
if x == None:
return 0 # 使用默认值调用函数,接下来的代码将不被执行
print(f'x={x}')
if x != None:
return f'x²={x**2}'
f()
0
x=2
'x²=4'
return 之后的表达式可以是多个表达式用逗号隔开(其实是一个元组),可用赋值语句分别接收返回值:
def f(x):
return sum(x), max(x), min(x)
f([1,2,3,4])
(10, 4, 1)
sum_x, max_x, min_x = f([1,2,3,4])
sum_x, max_x, min_x
(10, 4, 1)
返回值可以是任何值。如果 return 之后的表达式中包含函数自身的调用,则该函数称为递归函数。详见 递归函数。
# 返回函数自身
def f(x):
print(x)
return f
f(1)(2)(3)
1
2
3
<function __main__.f(x)>
# 返回函数自身的调用
def f(x):
if x == 0:
return 0
else:
print(x-1)
return f(x-1)
f(3)
2
1
0
0
递归函数
函数 return 语句的表达式中包含函数自身的调用,则称该函数为递归函数。
递归函数必须设定退出条件,并且调用过程能够逐步达到退出条件,否则将引发 RecursionError。
例如定义一个计算阶乘的函数:
上述函数的退出条件是 x == 0
,并且 return 语句中函数调用的参数是 x-1
,第一层调用函数的参数 x
为 5,return 语句进入第二层调用,就变成了 x-1
为 4,依此类推,最终以 0 调用函数,达到了退出条件,但是值还没有最终返回,以退出条件下的返回值层层向上返回,最终得到结果。详情查看 递归函数。
def f(x):
if x == 0:
return 1
else:
return f(x-1)*x
f(5)
120
函数文档
关键字 def 引入一个函数定义。构成函数体的语句从下一行开始,并且必须缩进。
函数体的第一个语句可以是字符串文字(可选的),这个字符串文字即是函数的文档字符串。有些工具使用文档字符串自动生成在线或印刷文档,或者让用户以交互式的形式浏览代码。在你编写的代码中包含文档字符串是一种很好的做法,所以要养成习惯。
文档字符串的内容和格式的约定:
可以使用函数的 __doc__
属性或 help() 函数查看函数文档。
Help on function my_function in module __main__:
my_function()
Do nothing, but document it.
def my_function():
'Do nothing, but document it.'
pass
my_function.__doc__
'Do nothing, but document it.'
# 多行的函数文档
def my_func():
"""Do nothing, but document it.
No, really, it doesn't do anything.
"""
pass
print(my_func.__doc__)
Do nothing, but document it.
No, really, it doesn't do anything.
Help on function my_func in module __main__:
my_func()
Do nothing, but document it.
No, really, it doesn't do anything.
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
函数调用
函数名之后带一个圆括号,圆括号内,根据形参的类型(详见函数形参),给函数传递相应的实参,即可调用函数,执行函数体中的代码。
函数调用传递的实参分为:位置参数和关键字参数。
- 位置参数: 调用函数时,不带标识符(例如 name=)直接按形参位置传递给函数的参数。位置参数可使用
*
将可迭代对象的元素拆包传入函数,但元素个数不能多于可接收位置参数的形参个数,除非有接收多个位置参数的可变位置形参。
def f(a,b=None,*c,d=None):
print(f'a={a},b={b},c={c},d={d}')
f(*[1,2,3,4,5]) # d 只能接收关键字参数
a=1,b=2,c=(3, 4, 5),d=None
def f(a,b=None,*,c=None):
print(f'a={a},b={b},c={c}')
f(1,2)
f(1,2,3) # c 只能接收关键字参数
a=1,b=2,c=None
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-2-9cc318bcfef7> in <module>
2 print(f'a={a},b={b},c={c}')
3 f(1,2)
----> 4 f(1,2,3)
TypeError: f() takes from 1 to 2 positional arguments but 3 were given
- 关键字参数: 函数调用中前面带有标识符(例如 name=)传递给函数的参数。关键字参数可以使用
**
将字典里的元素传入函数,但元素个数不能多于可接收关键字参数的形参个数。关键字参数的标识符或字典的键,必须与可接收关键字参数的形参的名称相同,除非有可接收任意关键字参数的可变关键字形参。
def f(a,/,b=None,*,c=None):
print(f'a={a},b={b},c={c}')
f(1,c=3,b=2) # a 仅限位置传参
f(a=1,c=3,b=2)
a=1,b=2,c=3
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-7-3c39aaeac558> in <module>
3
4 f(1,c=3,b=2) # a 仅限位置传参
----> 5 f(a=1,c=3,b=2)
TypeError: f() got some positional-only arguments passed as keyword arguments: 'a'
def f(a,/,b=None,**c):
print(f'a={a},b={b},c={c}')
# a 仅限位置,b 没有对应的名称,因此全部传给 c
f(1,**{'a':1,'d':4,'c':3})
a=1,b=None,c={'a': 1, 'd': 4, 'c': 3}
位置参数必须在关键字参数前面:
def f(a,b=None,**c):
print(f'a={a},b={b},c={c}')
f(b=2,1)
File "<ipython-input-13-59f3b615f9f3>", line 4
f(b=2,1)
^
SyntaxError: positional argument follows keyword argument
位置参数的位置不能传错,关键字参数的则可以任意位置:
def f(a,b=None,/,c=None,**d):
print(f'a={a},b={b},c={c},d={d}')
f(1,2,c=3,d=4)
f(2,1,d=4,c=3,e=5)
a=1,b=2,c=3,d={'d': 4}
a=2,b=1,c=3,d={'d': 4, 'e': 5}
标注并不影响传参规则,但按照标注传参是更明智的做法:
def f(a:int,b:str='b')-> str:
print(a*b)
f(2,'hi')
f(2,3)
hihi
6
lambda 函数
lambda 函数由 lambda 表达式创建,表达式的语法为:
lambda parameters: expression
形参 parameters
是可选的,表达式 expression
会在函数调用时被求值并作为返回值返回。
表达式必须显示地确定为一个表达式,而不能像 return 语句那样返回多个表达式(默认为一个元组)。
lambda 表达式会创建一个没有名字的函数,函数不能包含语句或标注,可以像调用函数一样直接调用它。
8
<function __main__.<lambda>(x, y)>
# 返回值必须显示地确定为一个表达式
f = lambda x: sum(x),max(x),min(x)
f([2,3,4])
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-5-1652333b2bc4> in <module>
1 # 必须显示地确定为一个表达式
----> 2 f = lambda x: sum(x),max(x),min(x)
3 f([2,3,4])
NameError: name 'x' is not defined
f = lambda x: (sum(x),max(x),min(x))
f([2,3,4])
(9, 4, 2)
lambda 函数通常在以函数作为参数的高阶函数中使用,没有名称,用完即弃。
a = ['2 2', '2 1 ','2 3']
sorted(a,key=lambda x:''.join(x.split()))
['2 1 ', '2 2', '2 3']
list(map(lambda x:''.join(x.split()), a))
['22', '21', '23']
生成器函数
函数定义中使用了 yield 语句,该定义创建的函数是生成器函数。生成器函数通常也直接叫生成器。
当一个生成器函数被调用的时候,它返回一个迭代器,也称为生成器(全称是生成器迭代器,下面所说生成器均指生成器迭代器)。然后通过这个生成器来控制生成器函数的执行。
生成器是一个迭代器,也是一个可迭代对象。但一个生成器生成的 “元素” 只能被使用一次,原因如下:
-
迭代生成器的时候,生成器函数开始执行,执行到 yield,然后执行被挂起,给生成器的调用者返回 yield 之后的表达式的值。挂起后,所有局部状态都被保留下来,包括局部变量的当前绑定,指令指针,内部求值栈和任何异常处理的状态。
-
继续迭代生成器,生成器函数从挂起状态继续执行,执行到 yield,然后执行又被挂起,给生成器的调用者返回 yield 之后的表达式的值。
-
生成器迭代完成时,引发 StopIteration。
在一个生成器函数中,return 语句表示生成器已完成并将导致 StopIteration 被引发。返回值(如果有的话)会被当作一个参数用来构建 StopIteration 并成为 StopIteration.value 属性。
0
def f(n):
yield n
g = f(0)
print(f)
print(g)
<function f at 0x000002480120ACA0>
<generator object f at 0x000002480122E040>
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-3-c91425ed1388> in <module>
----> 1 next(g) # 迭代结束
StopIteration:
def f(n):
yield n
n += 1
yield n
n += 1
yield n
# 生成器已完成,后面的不被执行
return 'end'
n += 1
yield n
g = f(0)
while True:
try:
print(next(g))
except StopIteration as s:
print(s.value) # StopIteration.value 属性
break
# 迭代结束,不能再次迭代生成器
next(g)
0
1
2
end
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-10-3ebb4e469fc1> in <module>
19 break
20 # 迭代结束,不能再次迭代生成器
---> 21 next(g)
StopIteration:
yield from
将可迭代对象中的每一项作为生成器的迭代项:
def f(*args):
yield from args
g = f(1,2,3)
next(g),list(g)
(1, [2, 3])
def f(arg):
yield from arg
g = f('123')
print(list(g))
# g 使用结束,再次使用什么也没有,创建了一个空列表
print(list(g))
# 如要再次使用可再创建一个生成器
list(f('123'))
['1', '2', '3']
[]
['1', '2', '3']