1.函数概述
一、什么是函数
简单地说,函数就是个黑匣子,它接收输入(参数),然后执行特定任务以完成特定功能,最后生成
输出(返回值)。其中,输入(参数)和输出(返回值)都是可选的,也就是说,可以有也可以没有。
函数就是执行特定任务以完成特定功能的一段代码。可以在程序中将某段代码定义成函数,并指定一个
函数名及接收的输入(参数),这样,就可以在程序的其它地方通过函数名多次调用并执行该段代码了,每次调用并执行后,都会根据接收的输入(参数)执行特定任务以完成特定功能从而生成相应的输出(返回值)
Python语言已经定义了很多内置函数。我们可以在程序中通过函数名直接调用这些内置函数。
例如:当我们调用内置函数id()时,输入(参数)是任意的对象,完成的特定功能是获取输入(参数)的
唯一标识,输出(返回值)是输入(参数)的唯一标识
二、为什么需要函数
1.复用代码
如果在程序中需要多次完成某个特定的功能,我们无需把这个特定功能的相关代码在多个地方编写多次,
完全可以把这个特定功能的相关代码定义成函数,然后在多个地方调用该函数,每次调用都会把函数对应的
相关代码执行一遍。
2,隐藏实现细节
函数就是个黑匣子,将实现细节隐藏起来了。很多时候我们无需关注函数的实现细节,只需关注其接收
的输入(参数)及生成的输出(返回值)就可以了。
3,提高可维护性
把完成某段特定功能的代码定义为函数后,如果需要对这段代码进行修改,只需要在一个地方进行修改,
提高了程序的可维护性。否则,你需要找到这段代码的多个不同地方,每个地方都要进行同样的修改,
费事费力还容易出错。
4,提高可读性、便于调试
每个函数都对应一段完成特定功能的代码,提高了程序的可读性,也便于程序调试。
2. 函数定义和调用
一、定义函数
定义函数的语法格式:
def 函数名([形式参数1,形式参数2,...,形式参数n]):
函数体
其中,def是Python定义的关键字。
关于函数名的说明:
(1)每个函数都有一个函数名,用于函数的调用。
(2)函数名属于标识符,因此,必须遵守标识符的命名规则,推荐遵守标识符的命名规范。此外,函数名最好是动宾格式,以表明函数完成的特定功能,例如:handle_message、print_result。
关于形式参数的说明:
(1)形式参数简称形参。
(2)形参用于在调用函数时接收输入,也就是接收传递的实际参数(简称实参)。
(3)形参用中括号括起来,表示形参时可选的,也就是说,可以定义也可以不定义。
(4)形参的本质是变量,其作用域(起作用的范围)仅限于函数体。
关于函数体的说明:
(1)函数体是用于执行特定任务以完成特定功能的主体代码。
(2)函数体对应的代码块必须缩进。
(3)如果函数需要有输出(返回值),可以在函数体内通过语句return xxx进行返回,同时结束函数体的执行。如果函数不需要有输出(返回值),可以在函数体内通过语句return直接结束函数体的执行,或者让函数体正常执行结束,其实,函数在这两种情况下都是有返回值的,其返回值都是None。
(4)函数体在调用函数时才会被执行,因此,定义函数不会改变程序的执行流程。
二、调用函数
调用函数时,每个实参都被用于初始化相应的形参。所有形参都被初始化之后,函数体对应的代码块
被执行。程序的执行流会跳转到定义函数的函数体内,执行函数体对应的代码块,执行完函数体后再跳回到调用函数的地方,继续执行下一条语句。
如果实参对象是可变类型,在函数体内对形参对象的任何修改其实就是对实参对象的修改。
3、 位置实参
调用函数时,可以根据每个形参在所有形参中的位置传递对应位置的实参,从而用每个实参初始化对应位置的形参,这样的实参称为位置实参
def f(a, b, c):
print('a =', a, 'b =', b, 'c =', c)
f(2, 5, 8)
4、关键字实参
调用函数时,传递的实参的形式可以为:形参名=实参值,从而用指定的实参值初始化指定名称的形参,这样的实参称为关键字实参。
由于关键字实参中指定了形参名,所以实参和形参的匹配关系更加清晰,而且每个关键字实参在所有关键字实参中的位置可以是任意的。
调用函数时,可以组合使用位置实参和关键字实参。但是,位置实参必须位于关键字实参之前。否则,无法根据位置来匹配位置实参和对应的形参。
def f(a, b, c):
print('a =', a, 'b =', b, 'c =', c)
f(a = 2, b = 5, c = 8)
f(b = 5, c = 8, a = 2)
f(c = 8, b = 5, a = 2)
f(2, 5, c = 8)
5、带默认值的形参
定义函数时,可以给形参设置默认值,这样,调用函数时如果不传递对应的实参,就会使用设置的默认值初始化形参。
给形参设置默认值的语法格式为:形参=默认值。
给形参设置默认值之后,可以简化函数的调用,只有与默认值不符的形参才需要传递额外的实参。
In [1]: def f1(a, b = 5):
...: print('a =', a, 'b =', b)
...:
...: f1(2, 6) # a = 2 b = 6
...: f1(2) # a = 2 b = 5
...:
a = 2 b = 6
a = 2 b = 5
In [2]: def f2(a, b = 5, c = 8):
...: print('a =', a, 'b =', b, 'c =', c)
...:
...: f2(2, 6, 9) # a = 2 b = 6 c = 9
...: f2(2) # a = 2 b = 5 c = 8
...: f2(2, 6) # a = 2 b = 6 c = 8
...: f2(2, c = 9)# a = 2 b = 5 c = 9
...:
a = 2 b = 6 c = 9
a = 2 b = 5 c = 8
a = 2 b = 6 c = 8
a = 2 b = 5 c = 9
定义函数时,没有设置默认值的形参必须位于设置了默认值的形参之前。否则,无法根据位置来匹配位置实参和对应的形参。
当函数有多个形参时,把变化大的形参放在前面,把变化小的形参放在后面,变化小的形参就可以设置默认值。
定义函数时,给形参设置的默认值就被计算出来了。因此,如果给形参设置的默认值是可变类型的对象,
并且前一次调用函数时在函数体内修改了形参的默认值,那么修改后的值将作为下一次调用函数时形参的默认值
In [1]: def fun1(L = []):
...: L.append(18)
...: print(L)
...:
...: fun1()
...: fun1()
...: fun1()
...:
[18]
[18, 18]
[18, 18, 18]
不要把形参的默认值设置为可变类型的对象。
In [2]: def fun2(L = None):
...: if L is None:
...: L = []
...: L.append(18)
...: print(L)
...:
...: fun2()
...: fun2()
...: fun2()
...:
[18]
[18]
[18]
6、函数的定义之使用*定义关键字形参
定义函数时,可以在所有形参的某个位置添加一个*,这样,后面的所有形参都被定义为 只能接收关键字实参的关键字形参。
In [1]: def f(a, b, *, c, d):
...: print('a =', a, 'b =', b, 'c =', c, 'd =', d)
...:
In [2]: f(1, 2, c = 3, d = 4)
a = 1 b = 2 c = 3 d = 4
In [3]: f(1, 2, 3, 4)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-d4def503b619> in <module>()
----> 1 f(1, 2, 3, 4)
TypeError: f() takes 2 positional arguments but 4 were given
7、个数可变的位置形参
- 定义函数时,可能无法事先确定传递的位置实参的个数,在这种情况下,可以在形参前添加一个*,将形参定义为个数可变的位置形参,从而可以接收0个或任意多个位置实参。这些位置实参会将个数可变的 位置形参初始化为一个元组。
In [1]: def f(*args):
...: print(args)
...:
...: f() # ()
...: f(1) # (1,)
...: f(1, 2, 3) # (1, 2, 3)
...:
()
(1,)
(1, 2, 3)
- 定义函数时,最多只能定义一个个数可变的位置形参
In [1]: def fun(*arg1, *arg2):
...: print(arg1, arg2)
File "<ipython-input-2-f1f6926b58d6>", line 1
def fun(*arg1, *arg2):
^
SyntaxError: invalid syntax
3.通常,把个数可变的位置形参定义为最后一个形参,以便接收所有剩余的位置实参
In [1]: def fun1(a, b, *c):
...: print('a =', a, 'b =', b, 'c =', c)
...:
...: fun1(1, 2, 3, 4, 5)
...:
a = 1 b = 2 c = (3, 4, 5)
如果个数可变的位置形参不是最后一个形参,那么其后面的所有形参都被定义为只能接收关键字实参的关键字形参。如果向这些关键字形参传递位置实参,所有的位置实参都会被算作个数可变的,从而导致关键字实参的缺失.
In [1]: def fun2(a, *b, c, d):
...: print('a =', a, 'b =', b, 'c =', c, 'd =', d)
...:
In [2]: fun2(1, 2, 3, 4, c = 5, d = 6)
a = 1 b = (2, 3, 4) c = 5 d = 6
In [3]: fun2(1, 2, 3, 4, 5, 6)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-60c1a5b7b6ba> in <module>()
----> 1 fun2(1, 2, 3, 4, 5, 6)
TypeError: fun2() missing 2 required keyword-only arguments: 'c' and 'd'
8、将序列的元素转换成位置实参,使用星号前缀
调用函数时,可以在序列前面添加一个*,从而将序列中的每个元素都转换为一个单独的位置实参。
In [1]: def f(a, b, c):
...: print('a =', a, 'b =', b, 'c =', c)
...:
In [2]: f(1, 2, 3)
a = 1 b = 2 c = 3
In [3]: L = [1, 2, 3]
In [4]: f(L[0], L[1], L[2])
a = 1 b = 2 c = 3
In [5]: f(*L)
a = 1 b = 2 c = 3
In [6]: f(L)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-6-e367f306d1e6> in <module>()
----> 1 f(L)
TypeError: f() missing 2 required positional arguments: 'b' and 'c'
注意和个数可变的位置形参进行区分。个数可变的位置形参是在定义函数时使用,使用*将序列中的每个元素都转换为位置实参是在调用函数时使用。
In [1]: def fun(*args):
...: print(args)
...:
In [2]: L = [1, 2, 3]
In [3]: fun(L)
([1, 2, 3],)
In [4]: fun(*L)
(1, 2, 3)
9、个数可变的关键字形参
定义函数时,可能无法事先确定传递的关键字实参的个数,在这种情况下,可以在形参前添加两个*,将形参定义为个数可变的关键字形参,从而可以接收0个或任意多个关键字实参。这些关键字实参会将个数可变的关键字形参初始化为一个字典。
定义函数时,最多只能定义一个个数可变的关键字形参。
In [1]: def f(**kwargs):
...: print(kwargs)
...:
...: f()
...: f(a = 1)
...: f(a = 1, b = 2, c = 3)
...:
{}
{'a': 1}
{'a': 1, 'b': 2, 'c': 3}
因为调用函数时位置实参必须位于关键字实参之前,所以个数可变的位置形参必须位于个数可变的关键字形参之前。
In [1]: def fun(*args, **kwargs):
...: print(kwargs, args)
...:
In [2]: def fun(**kwargs, *args):
...: print(kwargs, args)
File "<ipython-input-3-1f5935ca5c2c>", line 1
def fun(**kwargs, *args):
^
SyntaxError: invalid syntax
10、将字典的元素转换成关键字实参,使用2个星号前缀
调用函数时,可以在字典前添加两个*,从而将字典中的每个键值对都转换为一个单独的关键字实参。
In [1]: def f(a, b, c):
...: print('a =', a, 'b =', b, 'c =', c)
...:
In [2]: d = {'a': 1, 'b': 2, 'c': 3}
In [3]: f(d)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-e8148b646fd4> in <module>()
----> 1 f(d)
TypeError: f() missing 2 required positional arguments: 'b' and 'c'
In [4]: f(a = d['a'], b = d['b'], c = d['c'])
a = 1 b = 2 c = 3
In [5]: f(**d)
a = 1 b = 2 c = 3
注意和个数可变的关键字形参进行区分。个数可变的关键字形参 是在定义函数时使用,使用**将字典中的每个键值对都转换为关键字实参是在调用函数时使用。
先将字典中的每个键值对都转换为一个单独的关键字实参
再用这些关键字实参将个数可变的关键字形参初始化为一个字典
In [1]: def fun(**kwargs):
...: print(kwargs)
...:
In [2]: d = {'a': 1, 'b': 2, 'c': 3}^M
...: fun(**d)
...:
{'a': 1, 'b': 2, 'c': 3}
11 、函数的各种参数大总结
In [1]: def f1(a, b = 5, *args, **kwargs):
...: print('a =', a, 'b =', b, 'args =', args, 'kwargs =', kwargs)
...:
...: f1(2, 6, 7, 8, c = 9)
...: f1(2)
...:
a = 2 b = 6 args = (7, 8) kwargs = {'c': 9}
a = 2 b = 5 args = () kwargs = {}
In [2]: def f2(a, b = 5, *, c, **kwargs):
...: print('a =', a, 'b =', b, 'c =', c, 'kwargs =', kwargs)
...:
...: f2(*(3, 6), **{'c': 8, 'd': 10})
...: f2(3, c = 8, d = 10)
...:
a = 3 b = 6 c = 8 kwargs = {'d': 10}
a = 3 b = 5 c = 8 kwargs = {'d': 10}
12、占位符 pass
pass语句什么都不做,它只是一个占位符,用在语法上需要语句的地方,
例如:
1.if语句的条件执行体
2.for-in语句的循环体
3.定义函数时的函数体
有时候可能还没有想好上述相关语句该怎么写,就可以先使用pss语句作为占位符,以确保程序可以运行,等想好了之后再把pass语句替换掉
age = 23
if age > 18:
pass
for i in range(8):
pass
def do_something():
pass
13、函数的定义之文档字符串
一、什么是文档字符串
对于函数、模块、类或方法,位于其第一行的字符串被称为文档字符串,通常用三个引号表示。
文档字符串用于对函数、模块、类或方法进行解释说明。
之所以称为"文档“字符串,是因为可以使用工具根据文档字符串自动地生成文档。
应该养成编写文档字符串的习惯,以提高程序的可读性。
通过属性doc可以访问文档字符串。
调用内置函数help()得到的帮助信息中会包含文档字符串
二、函数的文档字符串的常见内容和格式约定
1.第一行是简明扼要的总结。
2.第一行的首字母大写,第一行以句号结尾。
3.如果文档字符串包含多行,第二行是空行,从第三行开始是详细的描述。
更多关于文档字符串的约定,可参考PEP257:https://www.python.org/dev/peps/pep-0257
In [1]: len.__doc__
Out[1]: 'Return the number of items in a container.'
In [2]: help(len)
Help on built-in function len in module builtins:
len(obj, /)
Return the number of items in a container.
In [3]: def form_complex(real = 0.0, imag = 0.0):
...: """Form a complex number.
...:
...: Keyword arguments:
...: real -- the real part(default 0.0)
...: imag -- the imaginary part(default 0.0)
...: """
...: pass
...:
In [4]: form_complex.__doc__
Out[4]: 'Form a complex number.\n\n Keyword arguments:\n real -- the real part(default 0.0)\n imag -- the imaginary part(default 0.0)\n '
In [5]: help(form_complex)
Help on function form_complex in module __main__:
form_complex(real=0.0, imag=0.0)
Form a complex number.
Keyword arguments:
real -- the real part(default 0.0)
imag -- the imaginary part(default 0.0)
14、函数的定义之函数注解
一、什么是函数注解
定义函数时,为了让形参或返回值的类型或作用更加清晰,可以给形参或返回值添加函数注解,
从而对形参或返回值做解释说明,以帮助函数文档化。
函数注解是可选的,可以添加也可以不添加。
函数注解可以是任意的表达式。
解释器会忽略函数注解,因此解释器并不会使用函数注解来检查实参的类型和返回值的类型。
更多关于函数注解,可参考PEP3107:https://www.python.org/dev/peps/pep-3107/
二、添加函数注解
给形参添加函数注解的方式为:在形参后面添加:和任意的表达式。
给返回值添加函数注解的方式为:在)的后面添加->和任意的表达式。
三、访问函数注解
通过属性__annotations__可以访问函数注解。
调用内置函数heLp()得到的帮助信息中会包含函数注解。
In [1]: def f(a: 'string type', b: int) -> 'join a with b':
...: return a + str(b)
...:
In [2]: print(f('hello', 12.3))
hello12.3
In [3]: print(f.__annotations__)
{'a': 'string type', 'b': <class 'int'>, 'return': 'join a with b'}
In [4]: help(f)
Help on function f in module __main__:
f(a:'string type', b:int) -> 'join a with b'