函数进阶
一、变量的作用域
在Python脚本中,每个变量都自己作用域,根据作用范围不同分为:局部变量和全局变量。
1.1 局部变量
局部变量,在函数体中定义的变量,作用范围只在函数中。
1.1.1 在全局访问局部变量
- 不可变数据类型
# 定义函数,在函数体定义一个变量,变量的类型为不可变
def func():
global a # 声名局部变量为全局变量
a = 0
print(f'局部a={a};a的ID={id(a)}')
# 调用函数
func() # 在全局访问局部前,必须调用函数
print(f'全局a={a};a的ID={id(a)}')
''' 输出结果:
局部a=0;a的ID=140736419202704
全局a=0;a的ID=140736419202704
'''
在全局访问局部变量,必须先调用函数,创建局部变量,如此才可以在全局访问这个局部变量!
- 可变数据类型
def func():
global lst # 声名局部变量为全局
lst = [1, 2]
print(f'局部lst={lst};lst的ID={id(lst)}')
func()
print(f'全局lst={lst};lst的ID={id(lst)}')
''' 输出结果:
局部lst=[1, 2];lst的ID=2039571804800
全局lst=[1, 2];lst的ID=2039571804800
'''
1.1.2 在全局中修改局部变量
- 不可变数据类型
# 定义函数
def func():
global a
a = 0
print(f'局部a={a};a的ID={id(a)}')
func()
print(f'修改之前的全局a={a};a的ID={id(a)}')
# 在全局修改局部变量
a = 1
print(f'修改之后的全局a={a};a的ID={id(a)}')
- 可变数据类型
# 定义函数
def func():
global lst
lst = [1, 2]
print(f'局部lst={lst};lst的ID={id(lst)}')
func()
print(f'修改之前的全局lst={lst};lst的ID={id(lst)}')
# 在全局修改局部变量
lst.append(3)
print(f'修改之后的全局lst={lst};lst的ID={id(lst)}')
全局修改局部变量:
如果全局在原有内存空间上进行修改,那么局部会重新创建一个原有变量的新空间;
如果全局创建一个新的空间,那么局部保持不变。
1.2 全局变量
全局变量是指定在整个脚本中可以任意访问的变量,定义在函数之外。
1.2.1 在局部访问全局变量
- 不可变数据类型
# 定义函数,在函数中访问全局变量
def func1():
print(f'局部a={a};a的ID={id(a)}')
# 定义变量
a = 0
func1()
- 可变数据类型
# 定义函数,在函数中访问全局变量
def func1():
print(f'局部a={a};a的ID={id(a)}')
# 定义变量
lst = [1, 2]
func1()
在局部访问全局变量,不需要加上
global
,局部可以直接访问!
1.2.2 在局部修改全局变量
- 不可变数据类型
# 定义函数,在函数中访问全局变量
def func1():
global a # 需要加上global
print(f'修改全局之前,局部a={a};a的ID={id(a)}')
a = 1
print(f'修改全局之后,局部a={a};a的ID={id(a)}')
# 定义变量
a = 0
print(f'调用之前,全局a={a};a的ID={id(a)}')
func1()
print(f'调用之后,全局a={a};a的ID={id(a)}')
- 可变数据类型
# 可变数据类型
def func1():
print(f'修改全局之前,局部lst={lst};lst的ID={id(lst)}')
lst.append(3)
print(f'修改全局之后,局部lst={lst};lst的ID={id(lst)}')
# 定义变量
lst = [1, 2]
print(f'调用之前,全局lst={lst};lst的ID={id(lst)}')
func1()
print(f'调用之后,全局lst={lst};lst的ID={id(lst)}')
局部修改全局变量:
- 如果全局变量的指向改变,则需要加上
global
- 如果全局变量的指向不改变,则不加
global
二、多函数的执行流程
- 函数之间共享全局变量
# 修改全局变量
def update():
lst.append(3)
print(f'修改后lst={lst}')
# 访问全局变量
def access():
print(f'访问lst={lst}')
# 全局变量
lst = [1, 2]
# 调用修改函数
update()
# 调用访问函数
access()
''' 输出结果:
修改后lst=[1, 2, 3]
访问lst=[1, 2, 3]
'''
函数之间是共享全局变量的!
- 返回值传入到另一个函数中
# 定义函数,且返回100
def func1():
return 100
# 定义函数,定义形参
def func2(arg):
print(arg)
# 将第一个函数的返回值传入到第二个函数中
func2(func1()) # 100
三、函数的返回值
- 没有
return
# 定义函数,不写return
def func():
pass
print(func()) # None
return
多个值
# 定义函数,return多个值
def func():
return 11, 'a', [22]
# 调用函数,并将函数的返回值保存到变量中
a = func()
print(a, type(a)) # (11, 'a', [22]) <class 'tuple'>
- 多个
return
:函数的return
会跳出函数体,return
之后的代码不会执行
def func():
return 1
return 2
return 3
a = func()
print(a) # 1
if
中的retrun
案例:判断两个数的大小,将较大的数字返回。
def func(x, y):
if x > y:
return x
else:
return y
a = func(1, 2)
print(a) # 2
b = func(5, 3)
print(b) # 5
四、函数的参数
函数的参数分为两种:实际参数和形式参数
4.1 实参
实参指的是在函数调用时传入的参数
- 位置实参
def func(a, b, c):
print(f'a={a}, b={b}, c={c}')
func(1, 2, 3)
- 位置实参必须按传参的顺序传入
- 位置实参的传入个数必须与形参个数相同
- 关键字实参
def func(a, b, c):
print(f'a={a}, b={b}, c={c}')
func(1, b=2, c=3)
func(1, c=3, b=2)
func(1, 3, b=3, c=3) # 报错,调用一次函数,参数只能传入一次
func(a=1, 2, c=3) # 位置实参只能在关键字实参之前传入
func(a=1, b=2, 3) # 位置实参只能在关键字实参之前传入
- 关键字实参必须在位置实参之后传入
- 关键字实参传入与传入的顺序无关
- 序列转为位置实参
def func(a, b, c):
print(f'a={a}, b={b}, c={c}')
# 1. 字符串
str1 = 'yes'
func(*str1) # a=y, b=e, c=s
# 2. 列表
lst = [11, 22, 33]
func(*lst) # a=11, b=22, c=33
# 3. 元组
t = 22, 'a', [1]
func(*t) # a=22, b=a, c=[1]
- 字典转为关键字实参
def func(a, b, c):
print(f'a={a}, b={b}, c={c}')
# 字典的key必须与形参一致
d = dict(
a=11,
b=22,
c=33
)
func(*d) # a=a, b=b, c=c # 只传入字典的key
func(**d) # a=11, b=22, c=33
4.2 形参
形参是指函数定义创建的参数。
- 位置形参
def func(a, b, c):
print(f'a={a}, b={b}, c={c}')
func(1, 2, 3) # a=1, b=2, c=3
func(a=1, b=2, c=3) # a=1, b=2, c=3
func(1, 2, c=3) # a=1, b=2, c=3
func(1, b=2, c=3) # a=1, b=2, c=3
- 仅位置形参:
Python3.8
中新加入的功能,/
之前的形参必须使用位置实参传入。
def func(a, b, /):
print(f'a={a};b={b}')
func(1, 2)
func(1, b=2) # 报错,使用了关键字实参
设计的目的:
- 限制调用者使用关键字实参传入;
- 因为创建者考虑到后续会修改形参,因此限制调用者使用关键字实参传入;
- 默认值形参
- 全部都是默认值形参
def func(a=1):
print(a)
func() # 1。使用形参的默认值
func(2) # 2。使用位置实参
func(a=3) # 3。使用关键字实参
- 与位置形参同时创建:位置形参必须在默认值形参之前定义
def func(a, b=1):
print(f'a={a};b={b}')
func(1)
func(1, 2)
func(1, b=3)
func(a=1, b=2)
func(a=1, 2)
- 默认值形参只计算一次
i = 5
def func(arg=i):
print(arg)
i = 6
func() # 5
- 默认值形参的值必须为不可变数据类型
需求:定义函数,将一个元素追加列表,函数的参数为元素和空列表。空列表使用默认值形参定义
# 错误演示
def func(arg, lst=[]):
lst.append(arg)
print(lst)
func(1) # [1]
func(2) # [1, 2]
func(3) # [1, 2, 3]
# 修改后代码
def func(arg, lst=None):
if lst is None:
lst = []
lst.append(arg)
print(lst)
func(1) # [1]
func(2) # [2]
func(3) # [3]
- 仅关键字形参:
*
之后的形参必须使用关键字实参传入
def func(*, a, b):
print(f'a={a};b={b}')
func(a=1, b=2) # a=1;b=2
func(1, 2) # 报错,*之后必须使用关键字实参传入
- 不定长位置形参:可以传入0个或者多个,相当于将实参组包传入到
args
def func1(*args):
print(args) # 这个args为实参
func1() # ()
func1(1) # (1,)
func1(2, 3, 5) # (2, 3, 5)
def func2(*args):
print(args)
print(*args) # 这个args为实参
func2(2, 3, 5)
''' 输出结果:
(2, 3, 5)
2 3 5
'''
- 不定长关键字形参
def func1(**kwargs):
print(kwargs)
func() # {}
func(a=1) # {'a': 1}
func(a=1, b=2) # {'a': 1, 'b': 2}
4.3 形参的总结
- 位置形参:
def func(a, b, c)
- 仅位置形参:
/
- 默认值形参:
a=默认值
- 仅关键字形参:
*
- 不定长位置形参:
*args
- 不定长关键字形参:
**kwargs
- 仅位置形参与仅关键字形参之间的关系
- 关系图
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
- 代码
def func(a, / , b, *, c):
print(f'a={a};b={b};c={c}')
func(1, 2, c=3)
func(1, b=2, c=3)
func(1, 2, 3) # 报错
func(a=1, b=2, c=3) # 报错
- 所有形参的关系
def func(a, /, b, *, *args, **kwargs): pass # 报错。*args
def func(*args, **kwargs): pass # 可以
def func(*args, **kwargs, a=1): pass # 报错
- 仅关键字形参 不能与 不定长位置形参 同时使用
- 不定长位置形参必须在不定长关键字形参之前
- 不定长形参必须在所有形参之后
五、递归
递归是一种编程思想。
递归的定义:在函数中调用函数本身。
递归的两个要素:
- 自调用
- 递归的出口
- 原理的解释
def func(n):
print(f'n={n};ID={id(n)}')
if n < 4:
func(n+1)
print(f'n={n};ID={id(n)}')
func(1)
递归的特性:
- 递归函数先调用后执行
- 所有的循环都可以使用递归来实现,但反过来不一定
- 递归在内存使用栈桢来保存(先进后出)
- 递归的逻辑简单,但是占用内存大
- 案例:使用递归计算n到1的和。比如:
4 + 3 + 2 + 1 = ?
'''
4 + 3 + 2 + 1 # func(4) = 4 + func(3)
3 + 2 + 1 # func(3) = 3 + func(2)
2 + 1 # func(2) = 2 + func(1)
1 # func(1)
'''
def func(n):
if n == 1:
return 1
return n + func(n-1)
func(4)
- 练习:求一个整数的阶乘。
4!
def func(n):
if n == 1:
return 1
return n * func(n-1)
func(4)
六、lambda
6.1 定义
定义匿名函数的关键字叫做lambda
,没有函数的名的函数。
当函数只有一句代码,且只有一个返回值 ,就可以使用匿名函数来定义。
6.2 语法
lambda 参数1, 参数2, ...,参数n: 表达式
lambda
的参数与普通函数的参数用法一致,可以有多个,也可以没有
def func():
return 200
print(func) # <function func at 0x000001FF0E551280>
print(func()) # 200
# 使用匿名来改写
f = lambda: 200
print(f) # <function <lambda> at 0x000001FF0E551310>
print(f()) # 200
6.3 案例
使用
lambda
定义函数,求两个数的和
def sum_(x, y):
return x + y
# 使用lambada来实现
f = lambda x, y: x + y
print(f(2, 3)) # 5
6.4 参数
- 无参数
print((lambda: 200)())
- 一个参数:传入什么就返回什么。打印”Hello,Python!“
print((lambda x: x)('Hello,Python!'))
- 包含默认值形参:计算
y=kx+b
def func(x, k=1, b=2):
return k*x+b
print((lambda x, k=1, b=2: k*x+b)(2)) # 4
print((lambda x, k=1, b=2: k*x+b)(2, k=2, b=3)) # 7
- 不定长位置形参
print((lambda *args: args)(1, 2, 3)) # (1, 2, 3)
print((lambda *args: args)((1, 2, 3))) # ((1, 2, 3),)
print((lambda *args: args)(*(1, 2, 3))) # (1, 2, 3)
- 不定长关键字形参
print((lambda **kwargs: kwargs)(a=1, b=2)) # {'a':1, 'b':2}
print((lambda **kwargs: kwargs)(**{'a':1, 'b':2})) # {'a':1, 'b':2}
七、高阶函数
将一个函数的引用作为实参传入到另一个函数,那么后者就是高阶函数。
Python是支持函数式编程范式。
def func1(x):
""" 将对象转换为str类型 """
return str(x)
def func2(y):
""" 将序列转换为列表 """
return list(y)
def func3(z):
""" 将序列转换为元组 """
return tuple(z)
# 使用一个高阶函数来实现以上三个函数的功能
def func(obj, function):
return function(obj)
print(func(11, str)) # '11'
print(func('Yes', list)) # ['Y', 'e', 's']
print(func('123', tuple)) # ('1', '2', '3')
lambda args: args)((1, 2, 3))) # (1, 2, 3)
* 不定长关键字形参
```python
print((lambda **kwargs: kwargs)(a=1, b=2)) # {'a':1, 'b':2}
print((lambda **kwargs: kwargs)(**{'a':1, 'b':2})) # {'a':1, 'b':2}
七、高阶函数
将一个函数的引用作为实参传入到另一个函数,那么后者就是高阶函数。
Python是支持函数式编程范式。
def func1(x):
""" 将对象转换为str类型 """
return str(x)
def func2(y):
""" 将序列转换为列表 """
return list(y)
def func3(z):
""" 将序列转换为元组 """
return tuple(z)
# 使用一个高阶函数来实现以上三个函数的功能
def func(obj, function):
return function(obj)
print(func(11, str)) # '11'
print(func('Yes', list)) # ['Y', 'e', 's']
print(func('123', tuple)) # ('1', '2', '3')