Python-函数及其参数

函数及其参数

作用

1、代码复用
2、隐藏实现细节
3、提高代码可维护性
4、提高代码可读性,便于调试

定义函数

def 函数名([形式参数1, 形式参数2, ..., 形式参数n]):
    函数体

函数名需要遵守命名规则和命名规范,最好使用动宾格式,例如handle_message、print_result等

关于形式参数的说明:
1、形式参数简称形参
2、形参用于在调用函数是接收输入,也就是接收传递的实际参数(简称实参)
3、形参用中括号括起来,表示形参是可选的,也就是说,可以定义也可以不定义
4、形参的本质是变量,其作用域仅限于函数体

关于函数体的说明:
1、函数体是用于执行特定任务,以完成特定功能的主体代码
2、函数体对应的代码块必须缩进
3、如果函数需要有输出(返回值),可以在函数体内通过语句return xxx进行返回,同时结束函数体的执行。如果函数不需要有输出,可以在函数体内通过语句return直接结束函数的执行,或者让函数体正常执行结束,其实在这两种情况下,都是有返回值的,返回值都是None对象
4、函数体只有在调用函数时才会被执行,定义函数并不会改变程序的执行流程

调用函数

调用函数时,每个实参都被用于初始化相应的形参,所有形参都被初始化后,函数体对应的代码块被执行。程序的执行流会跳转到定义函数的函数体内,执行函数体对应的代码块,执行完函数体后再跳到调用函数的地方,继续执行下一跳语句。

函数的调用

调用函数:位置实参

按顺序来,一个萝卜一个坑。

>>> def func(a, b, c):
...     print('a =', a, 'b =', b , 'c =', c)
...
>>> func(1, 2, 3)
a = 1 b = 2 c = 3

>>> func(3, 2, 1)
a = 3 b = 2 c = 1
>>>
>>> func('1', '2')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() missing 1 required positional argument: 'c'

调用函数:关键字实参

形参名 = 实参名,关键字实参的位置可以是任意的

>>> def func(a, b, c):
...     print('a =', a, 'b =', b , 'c =', c)
...
>>> func(a = 1, b = 2, c = 3)
a = 1 b = 2 c = 3

>>> func(b = 2, c = 3, a = 1)
a = 1 b = 2 c = 3

>>> func(b = 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() missing 2 required positional arguments: 'a' and 'c'

位置实参和关键字实参可以混用,但是位置实参必须在关键字实参之前

>>> def func(a, b, c):
...     print('a =', a, 'b =', b , 'c =', c)
...
>>>
>>> func(1, 2, c = 3)
a = 1 b = 2 c = 3
>>>
>>> func(1, b = 2, 3)
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument

>>> func(1, 2, b = 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() got multiple values for argument 'b'

调用函数:实参形参的传递

前面讲到,变量相当于标签,对于赋值语句:变量 = 对象,相当于给对象贴了一个标签,标签名就是变量名。
调用函数时把实参传递给形参从而用实参初始化形参,本质上执行了赋值语句:形参 = 实参对象,相当于给实参对象贴了一个标签,标签名就是形参名。
如果实参对象是可变类型,在函数体内对形参对象的任何修改,其实就是对实参对象的修改

#!/usr/bin/python3

def func(arg1, arg2):
    print('初始化形参后:', 'arg1 =', arg1, 'arg2 =', arg2)
    arg1 = arg1 * 2
    arg2.append(4)
    print('修改形参后:', 'arg1 =', arg1, 'arg2 =', arg2)

i = 10
L = [1, 2, 3]

print('调用函数前: i =', i, 'L =', L)

func(i, L)

print('调用函数后: i =', i, 'L =', L)
[root@lyucan ~]# ./4.py
调用函数前: i = 10 L = [1, 2, 3]
初始化形参后: arg1 = 10 arg2 = [1, 2, 3]
修改形参后: arg1 = 20 arg2 = [1, 2, 3, 4]
调用函数后: i = 10 L = [1, 2, 3, 4]                   ##实参L变了

对于实参对象是可变类型,如果想要函数体内改变形参时不改变实参,那么可以传递实参对象的值拷贝来实现

#!/usr/bin/python3

def func(arg1, arg2):
    print('初始化形参后:', 'arg1 =', arg1, 'arg2 =', arg2)
    arg1 = arg1 * 2
    arg2.append(4)
    print('修改形参后:', 'arg1 =', arg1, 'arg2 =', arg2)

i = 10
L = [1, 2, 3]

print('调用函数前: i =', i, 'L =', L)

func(i, L[:])                            ##实参的值拷贝

print('调用函数后: i =', i, 'L =', L)
[root@lyucan ~]# ./4.py
调用函数前: i = 10 L = [1, 2, 3]
初始化形参后: arg1 = 10 arg2 = [1, 2, 3]
修改形参后: arg1 = 20 arg2 = [1, 2, 3, 4]
调用函数后: i = 10 L = [1, 2, 3]                   ##实参L不变

如果需要在调用函数后返回多个返回值,可以在定义函数体内使用return语句返回由多个返回值组成的元组

#!/usr/bin/python3


def func(numbers):
    odds = []
    evens = []
    for number in numbers:
        if number % 2:
            odds.append(number)
        else:
            evens.append(number)
    return odds, evens


print(func([12, 123,214, 231321,41212,765, 6472,534]))
[root@lyucan ~]# ./5.py
([123, 231321, 765], [12, 214, 41212, 6472, 534])

定义函数:带默认值的位置形参

定义函数时,可以给形参设置默认值,在调用函数时,如果不传递对应的实参,就会使用设置的默认值初始化对象,给形参设置默认值后,可以简化函数的调用。
定义函数时,没有设置默认值的形参必须位于设置了默认值的形参之前,否则无法根据位置来匹配位置实参和对应的形参。
当函数有多个形参时,把变化大的形参放在前面,把变化小的形参放在后面,变化小的形参就可以设置默认值。
当不按顺序指定默认形参的实参时,需要指定形参的形参名

>>> def func(a, b = 5, c = 7):
...     print('a =', a, 'b =', b, 'c =', c)
...
>>> func(3)
a = 3 b = 5 c = 7

>>> func(3, 6, 8)
a = 3 b = 6 c = 8

>>> func(3, 6)
a = 3 b = 6 c = 7

>>> func(3, c = 8)
a = 3 b = 5 c = 8

>>> func(b = 6, c = 8)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() missing 1 required positional argument: 'a'

在定义函数的时候,给形参设置的默认值就已经被计算出来了,因此,如果给形参设置的默认值是可变类型的对象,并且前一次调用函数时在函数体内修改了形参的默认值,那么修改后的值将作为下一次调用函数时形参的默认值,所以,定义默认参数要牢记一点:默认参数必须指向不变对象!

>>> def func(L = []):
...     L.append('END')
...     print(L)
...
>>> func()
['END']
>>> func()
['END', 'END']
>>> func()
['END', 'END', 'END']
>>> func()
['END', 'END', 'END', 'END']


##修改如下
>>> def func(L = None):
...     if L is None:
...         L = []
...     L.append('END')
...     print(L)
...
>>> func()
['END']
>>> func()
['END']
>>> func()
['END']

定义函数:使用*定义命名关键字形参

定义函数时,可以在所有形参的某个位置添加一个*,这样,*后面的所有形参都被定义为只能接受关键字实参的命名关键字形参

>>> def f(a, b, *, c, d):
...     print(a, b, c, d)
...
>>> f(1, 2, c = 3, d =4)
1 2 3 4
>>>
>>> f(1, 2, 3, 4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes 2 positional arguments but 4 were given

定义函数:使用*定义个数可变的位置形参

定义函数时,可能无法事先确定传递的位置实参的个数,在这种情况下,可以在形参前面添加一个*,将形参定义为个数可变的位置形参,从而可以接收0个或任意多个位置实参。这些位置实参会将个数可变的位置形参初始化为一个元组。

>>> def f(*args):
...     print(args)
...
>>> f()
()
>>> f(1)
(1,)
>>> f(1, 2, 3)
(1, 2, 3)

定义函数时,最多只能定义一个个数可变的位置形参

>>> def f(*args, *args1):
  File "<stdin>", line 1
    def f(*args, *args1):
                 ^
SyntaxError: invalid syntax

通常,把个数可变的位置形参定义为最后一个形参,以便接收所有剩余的位置实参。

>>> def f(a, b, *args):
...     print(a, b, args)
...
>>> f(1, 2, 3, 4, 5)
1 2 (3, 4, 5)

如果个数可变的位置形参不是最后一个形参,那么其后面的所有形参都被定义为只能接受关键字实参的关键字形参

>>> def f(a, b, *c, d, e):
...     print(a, b, c, d, e)
...
>>>
>>> f(1, 2, 3, 4, d = 5, e = 6)
1 2 (3, 4) 5 6


>>> f(1, 2, 3, 4, 5, 6)                  ##3, 4, 5, 6全部被算做了个数可变的位置实参
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 2 required keyword-only arguments: 'd' and 'e'

调用函数:使用*将序列中的每个元素都转换为位置实参

调用函数时,可以在序列前面添加一个*号,从而将序列中的每个元素都转换为一个单独的位置实参。注意和个数可变的位置形参区分,个数可变的位置形参是在定义函数时使用,使用*将序列中每个元素都转换为位置实参是在调用函数时使用

>>> def f(a, b, c):
...     print(a, b, c)
...
>>> L = [1, 2, 3]
>>> f(*L)
1 2 3

>>> def f(*args):
...     print(args)
...
>>> L = [1, 2, 3]
>>>
>>> f(*L)                         ##返回的是一个元组
(1, 2, 3)

定义函数:使用**定义个数可变的关键字形参

定义函数时,可能无法事先确定传递的关键字实参的个数,在这种情况下,可以在形参前面添加一个**,将形参定义为个数可变的关键字形参,从而可以接收0个或任意多个关键字实参。这些关键字实参会将个数可变的关键字形参初始化为一个字典。

>>> def f(**kwargs):
...     print(kwargs)
...
>>> f(a = 1, b = 2, c = 3)
{'a': 1, 'b': 2, 'c': 3}

定义函数时,最多只能定义一个个数可变的关键字形参

>>> def f(**kwargs, **kwargs):
  File "<stdin>", line 1
    def f(**kwargs, **kwargs):
                     ^
SyntaxError: invalid syntax

调用函数时,位置实参必须位于关键字实参之前,所以个数可变的位置形参必须位于个数可变的关键字形参之前

>>> def f(**kwargs, *args):
  File "<stdin>", line 1
    def f(**kwargs, *args):
                    ^
SyntaxError: invalid syntax

调用函数:使用**将序列中的每个元素都转换为关键字实参

调用函数时,可以在字典前面添加一个**号,从而将字典中的每个key-value都转换为一个单独的关键字实参,注意和个数可变的关键字形参区分,个数可变的关键字形参是在定义函数时使用,使用**将字典中每个key-value都转换为关键字实参是在调用函数时使用

>>> def f(a, b, c):
...     print(a, b, c)
...
>>> f(1, 2, 3)
1 2 3

>>> f(a = 1, b = 2, c = 3)
1 2 3

>>> d = dict(a = 1, b = 2, c = 3)
>>> d
{'a': 1, 'b': 2, 'c': 3}

>>> f(d)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 2 required positional arguments: 'b' and 'c'

>>> f(**d)
1 2 3


>>> def f(**kwargs):
...     print(kwargs)
...
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>>
>>> f(**d)                          ##返回的是一个字典
{'a': 1, 'b': 2, 'c': 3}

当使用*加序列(列表,元组等)来传递位置实参时,被传递的实参和形参之间没有要求,但是使用**加字典来传递关键字实参时,**后面的字典的键值必须要和关键字形参的形参名相同
这个****args*kwargs没有任何关系,一个是定义形参的方法,一个是传入实参的方法,两者之间没有一一对应的关系

文档字符串

对于函数、模块、类或方法,位于其第一行的字符串被称为文档字符串,通常用单个引号括起来。
文档字符串用于对函数、模块、类或方法进行解释说明。

通过属性__doc__可以访问文档字符串,或者使用内置函数help()也可以访问文档字符串。

文档字符串编写规范:
1、第一行是简明扼要的总结
2、第一行首字母大写,第一行以句号结尾
3、如果文档字符串有多行,第二行是空行,从第三行开始进行详细的说明

函数注解

1、给形参添加注解:在形参后面添加:和任意的表达式
2、给返回值添加注解:在)的后面添加 ->和任意的表达式

>>> def f(a: 'string', b: int) -> 'join a with b':
...     result = a + str(b)
...     print(result)
...
>>> f('$', 11.0)
$11.0

访问函数注解
通过属性__annotations__访问函数注解

>>> f.__annotations__
{'a': 'string', 'b': <class 'int'>, 'return': 'join a with b'}
>>>

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 289211569@qq.com