一、函数
1、函数
函数是python中组织代码的最小单元
函数是实现模块化编程的基本组件
Python使用def语句定义函数
每个Python函数都有一个返回值,默认为None,也可以使用“return value”明确定定义返回值
def语句会创建一个函数对象,并同时创建一个指向函数的对象引用
函数也是对象,可以存储在组合数据类型中,也可以作为参数传递给其它函数
callable()可用于测试对象是否可调用
函数通过def定义,接着函数名,函数名后面用一对小括号列出参数列表,使用一个冒号开始函数体。
函数体是正常的Python语句,可以组合任意结构
return语句表示函数的返回值
函数有输入(参数)和输出(返回值),函数其实是一个代码单元,把输入转化为输出。
定义函数的时候并不会执行函数体,当调用函数时才会执行函数体
函数通过函数名来调用,函数名后面一对小括号里面传入实参
In [2]: def f1(): ...: print("hello") ...: In [3]: f1 Out[3]:In [4]: f1() hello In [5]: a = f1 In [6]: b = f1() hello In [7]: a Out[7]: In [8]: b In [9]: type(a) Out[9]: function In [10]: type(b) Out[10]: NoneType In [15]: callable(a) # 实现了__call__()方法就可调用 Out[15]: True
2、对于Python的函数,我们需要记住的是
1)函数的默认返回值是None。
2)python是一个自上而下逐行解释并执行的语言。因此,函数的定义必须在函数被调用之前。同名的函数,后定义的会覆盖前面定义的。
3)程序执行的时候,遇到函数定义只会先将函数整体读进内存,并不立刻执行。等到函数被调用的时候才执行函数体。
4)python函数的参数传递的是值传递还是引用传递。
函数参数传递本质上和变量整体复制一样,只是两个变量分别为形参a和实参b。那么,a=b后,a变了,b值是否跟着变呢?这取决于对象内容可变不可变
值传递:
指在调用函数时,将实际参数复制一份传递给函数,函数对参数进行修改将不会影响到实际参数
适用于不可变对象(如int, str,tuples等)作为参数传递时,例如元组
引用传递:
指调用函数时,将实际参数的地址传递给函数,函数对参数进行修改,将影响实际参数
适用于可变对象(如list,dict,类的实例等)作为参数传递时,例如列表
浅复制:(也叫影子复制)
只复制父对象,不会复制对象的内部的子对象
深复制:
复制对象及其子对象
赋值是引用传递
In [74]: l1 = [1, 2, 3] In [75]: l2 = l1 In [76]: l2 Out[76]: [1, 2, 3] In [77]: l3 = l1.copy() In [78]: l3 Out[78]: [1, 2, 3] In [79]: id(l3) # l3和l1应用的是不同内存对象 Out[79]: 140149284135624 In [80]: id(l1) Out[80]: 140149295996616 In [81]: id(l2) # l2和l1引用的是同一个内存对象 Out[81]: 140149295996616
二、函数的参数
对于函数,最重要的知识点莫过于参数了。
参数分为形式参数(形参)和实际参数(实参)。
def f1(a, b, c): pass f1(1, 2, 3)
其中,a,b,c就是形参,1,2,3就是实参,也就是实际要传递的参数。
In [27]: def add(x, y): ...: print(x + y) ...: return x + y ...: In [28]: add(3, 5) 8 Out[28]: 8 In [29]: add(3) --------------------------------------------------------------------------- TypeError Traceback (most recent call last)in () ----> 1 add(3) TypeError: add() missing 1 required positional argument: 'y' In [30]: add(3, 5, 8) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) in () ----> 1 add(3, 5, 8) TypeError: add() takes 2 positional arguments but 3 were given In [42]: add(3, "5") --------------------------------------------------------------------------- TypeError Traceback (most recent call last) in () ----> 1 add(3, "5") in add(x, y) 1 def add(x, y): ----> 2 ret = x + y 3 print('{} + {} = {}'.format(x, y, ret)) 4 return ret TypeError: unsupported operand type(s) for +: 'int' and 'str'
函数调用时,传入的实参必须和函数定义时的行参想匹配,如果不匹配会抛出TypeError.
Python中的形式参数有以下几种:
1、位置参数
通常在传递参数的时候我们按照参数的位置,逐一传递,这叫“位置参数”。
In [31]: def add(x, y): ...: ret = x + y ...: print('{} + {} = {}'.format(x, y, ret)) ...: return ret ...: In [32]: add(3, 5) 3 + 5 = 8 Out[32]: 8
2、关键字参数
而有时候我们会用“形参名”=“值”的方式传递参数,这叫“关键字参数或指定参数”。
In [33]: add(x=3, y=5) 3 + 5 = 8 Out[33]: 8 In [34]: add(y=3, x=5) # 关键字参数和顺序无关 5 + 3 = 8 Out[34]: 8
位置参数和关键字参数混用时关键字参数必须在位置参数后面
In [37]: add(3, y=5) 3 + 5 = 8 Out[37]: 8 In [38]: add(x=3, 5) File "", line 1 add(x=3, 5) ^ SyntaxError: positional argument follows keyword argument In [39]: add(3, x=5) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) in () ----> 1 add(3, x=5) TypeError: add() got multiple values for argument 'x'
3、默认参数
默认参数是为某些参数设定一个默认值,既可以减少参数传入量,也可以享受使用默认值的便利。
默认参数必须位于参数列表的最后部分!
默认参数是在函数定义时,指的是形参!
In [46]: def inc(base, x=1): ...: return base + x ...: In [47]: inc(3, 2) Out[47]: 5 In [48]: inc(3) Out[48]: 4 In [49]: inc(3, x=4) Out[49]: 7 In [56]: inc(base=4) Out[56]: 5 In [57]: inc(base=4, x=5) Out[57]: 9 In [50]: def inc(x=1, base): ...: return base + x ...: File "", line 1 def inc(x=1, base): ^ SyntaxError: non-default argument follows default argument
4、可变(动态)参数
Python的动态参数有两种,分别是*args和**kwargs,这里面的关键是一个和两个星号,而不是args和kwargs,实际上你可以使用*any或**whatever的方式,但就如self一样,潜规则我们使用*args和**kwargs。
可变参数是在函数定义时,指的是形参!
*args:位置可变参数;函数定义时参数名前加一个星号
一个星号表示接受任意个动态参数。调用时,会将实际参数打包成一个元组传入函数。
此时只能通过位置参数传参
In [63]: def sum(*args): ...: ret = 0 ...: for i in args: ...: ret += i ...: return ret ...: In [64]: sum() Out[64]: 0 In [65]: sum(1) Out[65]: 1 In [66]: sum(1, 2, 3, 8) Out[66]: 14
**kwargs:关键字可变参数;函数定义时参数名前加两个星号
两个星表示接受键值对的动态参数,数量任意。调用的时候会将实际参数打包成字典。
此时只能通过关键字参数传参
例如:
In [73]: def connect(**kwargs): ...: print(type(kwargs)) ...: for k, v in kwargs.items(): ...: print('{} => {}'.format(k, v)) ...: In [74]: connect(host="localhost", port=3300)host => localhost port => 3300 In [79]: connect("localhost", port=3300) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) in () ----> 1 connect("localhost", port=3300) TypeError: connect() takes 0 positional arguments but 1 was given
万能参数:
当*args和**kwargs组合起来使用,理论上能接受任何形式和数量的参数,在很多代码中我们都能见到这种定义方式。需要注意的是,*args必须出现在**kwargs之前。
In [83]: def fn(*args, **kwargs): ...: print(args) ...: print(kwargs) ...: In [84]: fn(2, 3, 5, x=11, y="xxj") (2, 3, 5) {'x': 11, 'y': 'xxj'} In [85]: def fn(**kwargs, *args): ...: print(args) ...: print(kwargs) File "", line 1 def fn(**kwargs, *args): ^ SyntaxError: invalid syntax
可变参数和普通参数混合使用:
In [91]: def fn(x, y, *args, **kwargs): ...: print(x) ...: print(y) ...: print(args) ...: print(kwargs) ...: In [92]: fn(2, 3, 4, 5, 6, a=1,b=2) 2 3 (4, 5, 6) {'a': 1, 'b': 2} In [95]: fn(2, 3) 2 3 () {} In [97]: fn(2, y=3) 2 3 () {} In [99]: fn(2, 3, 4) 2 3 (4,) {} In [100]: fn(2, 3, a=23) 2 3 () {'a': 23} In [107]: def fn(*args, x): # 位置可变参数是否可以在普通参数之前 ...: print(args) ...: print(x) ...: In [108]: fn(1, 2, 3) --------------------------------------------------------------------------- TypeError Traceback (most recent call last)in () ----> 1 fn(1, 2, 3) TypeError: fn() missing 1 required keyword-only argument: 'x' In [109]: fn(1, 2, x=3) (1, 2) 3 In [110]: fn(1, x=3) (1,) 3 In [111]: fn(x=3) () 3 In [112]: def fn(**kwargs, x): # 关键字可变参数是否可以在普通参数之前 ...: print(kwargs) ...: print(x) ...: File " ", line 1 def fn(**kwargs, x): ^ SyntaxError: invalid syntax
位置可变参数也可以在普通参数之前,但是在位置可变参数之后的普通参数变成了keyword-only参数(只能以关键字参数登入)
关键字可变参数不可以在普通参数之前(为什么?)
可变参数和默认参数混合使用:
In [113]: def fn(x=5, *args): ...: print(x) ...: print(args) ...: In [114]: fn(1) 1 () In [115]: fn(1,2) 1 (2,) In [116]: fn(1,2,3,4) 1 (2, 3, 4) In [117]: fn(x=3, 4) File "", line 1 fn(x=3, 4) ^ SyntaxError: positional argument follows keyword argument In [121]: def fn(*args, x=5): # 位置参数在默认参数之前没限制 ...: print(x) ...: print(args) ...: In [122]: fn() 5 () In [123]: fn(1, 2) 5 (1, 2) In [124]: fn(1) 5 (1,) In [125]: fn(1, x=3) 3 (1,) In [127]: def fn(**kwargs, x=5): ...: print(x) ...: print(kwargs) ...: File " ", line 1 def fn(**kwargs, x=5): ^ SyntaxError: invalid syntax In [128]: def fn(x=5, **kwargs): ...: print(x) ...: print(kwargs) ...: ...: In [129]: fn() 5 {} In [130]: fn(1) 1 {} In [131]: fn(1, a=2) 1 {'a': 2} In [132]: fn(x=1, a=2) 1 {'a': 2}
可变位置参数在默认参数之后,默认参数不能使用关键字传参
可变关键字参数不能在默认参数之前
当默认参数和可变参数一起出现时,默认参数相当于普通参数
小结:
函数的参数规则这么多,头都大了;这里我们建议的函数参数使用用法:
1)默认参数靠后
2)可变参数靠后
3)默认参数和可变参数不同时出现
不遵守不一定错,遵守代码可读性高
当我们需要同时使用默认参数和可变参数时怎么办?
我们通常这样处理:
In [139]: def connect(host='127.0.0.1', port=3306, user='root', password='', **kwargs): ...: pass ...: In [140]: def connect(**kwargs): ...: host = kwargs.pop('host', '127.0.0.1') ...:
三、参数解构
1、参数解构
调用函数时,传入实参时加一个星号,可以把iterable解构成位置参数
调用函数时,传入实参时加两个个星号,可以把dict解构成关键字参数
In [141]: def add(x, y): ...: ret = x + y ...: print('{} + {} = {}'.format(x, y, ret)) ...: In [142]: add(1, 2) 1 + 2 = 3 In [145]: t = [1, 2] In [146]: add(*t) 1 + 2 = 3 In [143]: t = [1, 2, 3] In [144]: add(*t) --------------------------------------------------------------------------- TypeError Traceback (most recent call last)in () ----> 1 add(*t) TypeError: add() takes 2 positional arguments but 3 were given In [147]: t = [(1, 2), (3, 4)] In [148]: add(*t) In [148]: add(*t) (1, 2) + (3, 4) = (1, 2, 3, 4) # 字典解构成关键字参数 In [153]: d = {'x':1, 'y':2} In [154]: add(**d) 1 + 2 = 3 In [155]: add(*d) x + y = xy
2、参数解构和可变参数混用
In [161]: def sum(*args): ...: ret = 0 ...: for i in args: ...: ret += i ...: return ret ...: In [162]: sum(*(1, 2)) Out[162]: 3 In [163]: sum(*[1, 2]) Out[163]: 3 In [165]: sum(*range(5)) Out[165]: 10
3、参数解构的限制
关键字参数解构,key必须是str
In [167]: def fn(**kwargs): ...: print(kwargs) ...: In [168]: fn(**{'a':1}) {'a': 1} In [170]: fn(**{12:1}) --------------------------------------------------------------------------- TypeError Traceback (most recent call last)in () ----> 1 fn(**{12:1}) In [211]: def fn(*args, **kwargs): ...: print(args) ...: print(kwargs) ...: In [212]: fn(*[1, 2, 3]) (1, 2, 3) {} In [214]: fn(*[1, 2, 3], **{'a':1, 'b':2}) (1, 2, 3) {'a': 1, 'b': 2}
4、keyword-only参数
星号之后的参数只能通过关键字参数传入
可变位置参数之后的参数也是keyword-only参数
只能通过关键字参数传入的参数就交keyword-only参数
keyword-only参数可以有默认值
In [179]: def fn(*, x): ...: print(x) ...: In [180]: fn(1) --------------------------------------------------------------------------- TypeError Traceback (most recent call last)in () ----> 1 fn(1) TypeError: fn() takes 0 positional arguments but 1 was given In [181]: fn(x=1) 1 In [182]: fn(1, x=2) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) in () ----> 1 fn(1, x=2) TypeError: fn() takes 0 positional arguments but 1 positional argument (and 1 keyword-only argument) were given In [183]:
keyword-only参数与其它参数混用:
In [188]: def fn(x, *, y): ...: print(x) ...: print(y) ...: In [189]: fn(1, y=2) 1 2 In [193]: def fn(x=1, *, y=2): ...: print(x) ...: print(y) ...: ...: In [194]: fn() 1 2 In [199]: def fn(x=1, *, y): # ...: print(x) ...: print(y) ...: ...: In [200]: fn(y=3) 1 3 In [201]: fn(3) --------------------------------------------------------------------------- TypeError Traceback (most recent call last)in () ----> 1 fn(3) TypeError: fn() missing 1 required keyword-only argument: 'y' In [202]: In [205]: def fn(*, x, y): # *号后可以有多个keyword-only参数 ...: print(x) ...: print(y) ...: In [206]: fn(x=1, y=2) 1 2 In [207]: def fn(x, y, *): # *号不能写在最后 ...: print(x) ...: print(y) ...: File " ", line 1 def fn(x, y, *): ^ SyntaxError: named arguments must follow bare *