函数
函数定义
由若干语句组成的语句块,函数名称、参数列表构成完成一定的功能
函数的作用
- 结构化变成对代码的基本封装,按照功能组织成一段代码
- 封装的目的为了复用,减少冗余代码
- 代码更加简洁,可读性高
函数的分类
- 内建函数,如max()
- 库函数,如math.ceil()
函数定义
- def语句定义
def 函数名(参数列表):
函数体(代码块)
[return 返回值]
- 函数名就是标识符,命名要求和python一样
- Python的函数如果没有return语句,隐式会返回一个None值
- 定义中的参数列表成为形式参数,只是一种符号表达,简称形参
函数调用
- 函数定义,只是声明了一个函数,它不会被执行,需要调用
- 调用的方式,就是函数名加上小括号,括号内写上参数
- 调用时写的参数式实际参数,是实实在在传入的值,简称实参
函数举例
def add(x, y):
result = x + y
return result
out = add(4, 5)
print(out)
- 定义了一个函数叫add,接收两个参数
- 计算的结果,通过返回值返回
- 调用通过函数名ad加两个参数,返回值可使用变量接收
- 定义需要在调用前,也就是调用时,已经被定义过了,否者会抛Error
- 函数是可调用对象,callable()
函数参数
- 参数调用时传入的参数要和定义的个数匹配(可遍参数例外)
位置参数
- def(x , y, z)调用时使用f(1, 3, 5)
- 按照参数定义顺序传入实参
关键值参数
- def f(x, y, z)调用时使用f(x=1, y=3, z=5)
- 使用形参的名字来传入实参的方式,如果使用了形参名字,那么传参的顺序可以和定义顺序不同
参数默认值
- 定义时,在形参后跟上一个默认值
def add(x=4, y=5)
return x+y
- 参数的默认值在未传入足够的实参时,对没有给定的参数传入默认值
可变参数
- 一个形参可以匹配任意个参数
- 位置参数的可变参数
def add(*nums):
sum = 0
for x in nums:
sum += x
print(sum)
add(3, 6, 9) #调用
- 在形参前使用*表示该形参时可变参数,
- 关键字参数的可变参数
def showconfig(**kwargs):
for k,v in kwagrs.items():
print('{}={}'.format(k,v))
showconfig(host='127.0.0.1', port='8080')
- 形参前使用**符号,表示可以接受多个关键字参数
- 收集的实参名称和值组成一个字典
- 可变参数的混合使用
def showconfig(username, *args, **kwargs):
print(username)
print(args)
for k,v in kwargs.items():
print('{}={}'.format(k,v))
# 调用,username按照位置参数取值,后面的先传递args的元组,然后传递给kwargs的字典
In [14]: showconfig('zhangsan', host='127.0.0.1', port='8080')
zhangsan
()
host=127.0.0.1
port=8080
keyword-only参数
- 如果在一个星号参数后,或者一个位置可变参数(不可以是可变关键字参数)后出现的普通参数,实际上已经不是一个普通的参数了,而是keyword-only参数
- 示例 ```python In [1]: def fn(args, x): # 注意args顺序 …: print(x) …: print(args) …:
In [2]: fn(3, 5) # 用普通位置参数传参报错
TypeError Traceback (most recent call last)
TypeError: fn() missing 1 required keyword-only argument: ‘x’
In [3]: fn(3,5,x=7) # 需要用关键字参数x=7传入才可以 7 (3, 5)
- 强制使用keyword-only参数
```python
In [4]: def fn1(*, x, y): # 注意*
...: print(x, y)
...:
In [5]: fn1(5, 6) # 使用普通位置参数传参报错
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-5-3ff6e7039ab2> in <module>()
----> 1 fn1(5, 6)
TypeError: fn1() takes 0 positional arguments but 2 were given
In [6]: fn1(x=5, y=6) # 必须使用关键值参数才可以
5 6
总结:
- 有位置可变参数和关键字可变参数
- 位置可变参数在形参前使用一个*号定义
- 关键字可变参数在形参前使用两个**号
- 位置可变参数和关键字可变参数都可以收集若干个实参,位置可变参数收集形成一个tuple,关键字可变参数收集形成一个dict
- 混合使用参数时,可变参数要放到参数列表的后面,普通参数需要放到参数列表的前面,位置可变参数需要放到关键字可变参数之前
函数参数顺序
- 普通位置参数—-缺省参数—-可变位置参数—-keyword-only参数—-可变关键值参数
- 示例 ```python In [7]: def fn(x, y, z=3, args, m=4, n, *kwargs): # 注意形参的定义 …: print(x, y, z, m, n) …: print(args) …: print(kwargs) …:
In [8]: fn(1,2,4) # 在*args后面的已经不是普通位置参数,而是keyword-only参数,所以必须指定k=v
TypeError Traceback (most recent call last)
TypeError: fn() missing 1 required keyword-only argument: ‘n’
In [9]: fn(1,2,n=4) # n的值传入keyword-only参数,则可以 1 2 3 4 4 () {}
In [10]: fn(1,2,10,11,n=4,t=11,q=12)
1和2按照位置参数赋值给x和y
10按照位置参数替换为z
11为可变位置参数被*args接收为tuple类型
m使用默认值,n=4为keyword-only参数赋值给n
t=11,q=12为可变关键值参数被**kwargs接收为dict类型
1 2 10 4 4 (11,) {‘t’: 11, ‘q’: 12}
-
参数解构
- 将复杂的数据结构解构成函数需要的参数
- 示例
```python
In [11]: def fn(x, y):
...: print(x+y)
...:
In [12]: t1 = (1,6,8)
In [13]: l1 = [8,3]
In [14]: fn(t1[1], l1[0])
14
- 给函数提供实参的时候,可以在集合类型前使用或者*,把集合类型的结构解开,提取出所有元素作为函数的实参
- 非字典类型使用*结构成位置参数
- 字典类型使用**结构成关键字参数
- 提取出来的元素数目要和参数的要求匹配,也要和参数的类型匹配 ```python In [45]: def add(x, y): …: print(x+y) …:
In [46]: dict1 = {‘x’:5, ‘y’:8}
In [47]: add(**dict1) 13
In [48]: add(*dict1.keys()) xy
In [49]: add(*dict1.values()) 13
<a name="d8ca4a4e"></a>
### 函数嵌套
-
作用
- 封装-数据隐藏,外部无法访问嵌套函数
- 贯彻diy原则,减少重复代码
- 闭包
```python
In [2]: def outer():
...: def inner():
...: print('inner')
...: print('outer')
...: inner()
...:
In [3]: outer()
outer
inner
In [4]: inner()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-4-bc10f1654870> in <module>()
----> 1 inner()
NameError: name 'inner' is not defined
总结
- 函数有可见范围,也叫作用域
- 内部函数不能直接被外部调用,会报NameError错误
函数作用域
- 一个标识符的可见范围,就是标识符的作用域
全局作用域
- 在整个程序运行环境中都可见
局部作用域
- 在函数、类等内部课件
- 局部变量使用范围不能超过其所在的局部作用域
In [9]: def outer2():
...: o = 65
...: def inner():
...: o = 97
...: print("inner {}".format(o))
...: print(chr(o))
...: inner()
...: print("outer {}".format(o))
...:
In [10]: outer2()
inner 97
a
outer 65
从上面的例子可以看出
- 外层变量作用域在内层作用域可见
- 内层作用域inner中,如果定义了o=97,相当于当前作用域重新定义了一个新的变量o,这个o并没有覆盖外层作用域outer中的o
n [12]: x =5 # 定义全局作用域x
In [13]: def foo():
...: x = x + 1 # 当本地作用域x同时出现在等号两边,意味着会查找本地作用域x,但此时x只是标识还没有定义,故会报错。
...: print(x)
...:
In [14]: foo()
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
<ipython-input-14-c19b6d9633cf> in <module>()
----> 1 foo()
<ipython-input-13-b321e3951b12> in foo()
1 def foo():
----> 2 x = x + 1
3 print(x)
4
UnboundLocalError: local variable 'x' referenced before assignment
问题:如何解决上面的问题?
- 定义全局作用域 global
In [21]: x = 5
In [22]: def foo():
...: global x
...: x += 2
...: print(x)
...:
In [23]: foo()
7
- 使用global关键字的变量,将foo内的x声明为外部的全局作用域
- 全局作用域中一定要有x的定义,如没有会报not found
闭包
自由变量:没有在本地作用域中定义的变量,例如定义在内层函数外的外层函数的作用域中的变量。
闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。
python2实现闭包示例:
In [4]: def counter():
...: c = [0]
...: def inc(): # 内部函数中使用到了c,这个c就是自由变量,此处就形成了闭包,这个c在调用结束后依旧存在
...: c[0] += 1 # c已经在counter函数中定义了,此处引用的是c元素的值,不是重新定义变量,因此不会报错
...: return c[0]
...: return inc
...: foo = counter() # 返回callable对象
...: print(foo())
...: print(foo())
...: c = 100
...: print(foo())
...:
1
2
3
注意:下面这段代码
In [1]: def counter():
...: count = 0
...: def inc():
...: count += 1
...: return count
...: return inc
...:
...:
In [2]: foo = counter()
In [3]: foo()
- 上面的代码会报错,使用global可以解决,但这使用的是全局变量,而不是闭包
In [5]: count = 0
...: def counter():
...: count = 0
...: def inc():
...: global count
...: count += 1
...: return count
...: return inc
...:
...:
In [6]: foo = counter()
In [7]: foo()
- 如果要对普通变量的闭包,python3中可以使用nonlocal
nonlocal关键字
- 使用了nonlocal关键字,将变量标记为在上一级作用域的局部变量,但不能是全局作用域的变量
In [9]: def counter():
...: count = 0
...: def inc():
...: nonlocal count
...: count += 1
...: return count
...: return inc
...:
...:
In [10]: foo = counter()
In [11]: foo()
Out[11]: 1
In [12]: foo()
Out[12]: 2
- count是外层函数的局部变量,被内部函数引用
- 内部函数使用nonlocal关键字声明count变量在上一级作用域中
- 可正常执行,且形成闭包
默认值作用域
In [14]: def foo(xyz=[]):
...: xyz.append(1)
...: print(xyz)
...:
In [15]: foo()
[1]
In [16]: foo()
[1, 1]
In [17]: xyz # 当前作用域没有xyz变量
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-17-8714e0ef31ed> in <module>()
----> 1 xyz
NameError: name 'xyz' is not defined
为什么第二次调用foo函数打印的是[1,1]?
- 因为函数也是对象,python把函数的默认值放到了属性中,这个属性就伴随着这个函数对象的整个生命周期
- 可以通过foo.defaults查看
- 函数在内存中的地址没有变
- 属性defaults中使用元组保存所有默认值,它不会因为在函数体内使用它而发声改变。
- 常用示例
In [26]: def foo(xyz=None, u ='abc'):
...: if xyz is None:
...: xyz = []
...: xyz.append(1)
...: print(xyz)
...:
In [27]: foo()
[1]
In [28]: foo([10])
[10, 1]
使用None来创建一个列表
如果传入一个列表,就修改这个列表。
通过值的判断就可以灵活的选择创建或者修改传入的对象。
变量名解析原则LEGB

- L — Local(function);本地作用域,局部作用域的local命名空间,函数调用时创建,调用结束消亡
- E— Enclosing;Python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
- G— Global;全局作用域,即一个模块的命名空间,模块被import时创建,解释器退出时消亡
- B— Build-in;内置模块的命名空间,生命周期从Python解释器启动时创建到解释器退出时消亡,例如print
依据就近原则,从下往上,从里向外,依次寻找
(1)内部函数可以直接在函数外部调用么? 不行!
(2)调用外部函数后,内部函数可以在函数外部调用吗 不行!
(3)内部函数可以在函数内部调用吗 可以
(4)内部函数在函数内部调用时,是否有先后顺序 有先后顺序
函数的返回值
- python函数使用return语句返回“返回值”
- 所有函数抖友返回值,如果没有return语句,隐式调用return None
- return语句并不一定是函数的语句块的最后一条语句。
- 一个函数可以存在多个return语句,但只有一条可以被执行
- 如果函数执行了return语句,函数就会返回,当被执行的return语句之后的其他语句就不会被执行了。
递归 Recursion
- 函数直接或者间接调用自身就是递归
- 递归需要有边界条件、递归前进段、递归返回段。否者就会进入无线调用
- 当边界条件不满足的时候,递归前进
- 当边界条件满足的时候,递归返回
递归调用的深度不宜过深
- pythondUI递归做了深度限制,以保护解释器
- 超过递归深度限制,抛出RecurisonError
- 可以通过sys.getrecursionlimit()
斐波那契数递归实现
i = 0
pre = 0
nxt = 1
print(pre)
print(nxt)
def fib(n, pre=0, nxt=1):
pre, nxt = nxt, pre + nxt
print(nxt)
if n == 2:
return
fib(n - 1, pre, nxt)
fib(10)
递归总结
- 递归是一种符合数学逻辑的表达
- 递归相对运行效率低,每一次调用函数都要开辟栈帧
- 递归有深度限制,如果递归层次太深,函数反复压栈,栈内存很快就溢出
- 如果有限次数的递归,可以使用递归调用,或者使用循环代替。
- 即使递归代码很简洁,但能不用则不用。
匿名函数
python借助lambda表达式构建匿名函数
格式
- lambda 参数列表:表达式
lambda x:x2
(lambda x:x2)(4) # 调用,4是参数
foo = lambda x,y:(x+y) ** 2 # 不推荐这么用,这样的需求可以使用普通函数
- lambda 参数列表:表达式
总结
- 使用lambda关键字来定义匿名函数
- 参数列表不需要小括号
- 冒号是用来分割参数列表和表达式的
- 不需要return,表达式的值就是匿名函数的表达式
- lambda表达式只能写在一行上,右边必须是个表达式,多以被称为单行函数。
In [101]: (lambda x:x**2)(4)
Out[101]: 16
In [106]: print((lambda x, y=3: x + y)(5))
8
In [111]: print((lambda *args: [x for x in args])(*range(5)))
[0, 1, 2, 3, 4]
In [110]: print((lambda *args: (x for x in args))(*range(5)))
<generator object <lambda>.<locals>.<genexpr> at 0x7ff77f729f50>
函数示例
字典扁平化处理
- 原字典:{‘a’: {‘b’: 1, ‘c’: 2}, ‘d’: {‘e’: 3, ‘f’: {‘g’: 4}}}
- 目标字典:{‘a.b’: 1, ‘a.c’: 2, ‘d.e’: 3, ‘d.f.g’: 4}
# 递归实现
src = {'a': {'b': 1, 'c': 2}, 'd': {'e': 3, 'f': {'g': 4}}}
def bianping(src, targetkey=''):
for k, v in src.items():
if isinstance(v, dict):
bianping(v, targetkey=targetkey + k + '.') # 递归调用
else:
target[targetkey + k] = v
target = {}
bianping(src)
print(target)
# 递归增加缺省值
src = {'a': {'b': 1, 'c': 2}, 'd': {'e': 3, 'f': {'g': 4}}}
# 增加dest的默认值
def bianping(src, dest=None, targetkey=''):
if dest == None:
dest = {}
for k, v in src.items():
if isinstance(v, dict):
bianping(v, dest, targetkey=targetkey + k + '.') # 递归调用
else:
dest[targetkey + k] = v
return dest
print(bianping(src))
