函数的参数
形参与实参
形参: 相当于变量名,定义的参数
实参: 相当于变量值,传入的值
赋值: 在调用时,将实参的内存地址绑定给形参
形参只能在函数体内使用
绑定关系只在调用时生效
形式:
1 func(int("1"), fuc(1,2), a = 1, 2) # 实参: 值
参数分类
位置形参: 直接定义的变量名, 必须传值
位置实参: 按顺序与形参一一对应
关键字实参: 按key =value与形参对应
位置实参与关键字实参混用: 1. 位置实参必须放在关键字实参之前;2. 不能重复传值
默认形参: 直接定义的变量名并赋值,可以不传值, 用于被多次使用的实参,一般不推荐可变类型(函数的返回值应符合期望,不应受其他代码的影响)
位置形参与默认形参混用: 位置形参必须放在默认形参之前
可变长度的*args
参数:
形参中的
*
保存溢出的位置实参为元组类型并赋值给*
后面的参数名,规范使用args
作为变量名,(封装),例 相加1
2
3
4
5
6
7
8def sum(*args): # args = (1, 2, 3, 4, 5)
res = 0
for item in args:
res += item
print(res)
sum(1, 2, 3, 4, 5)实参中的
*
拆分*
后面的可被for循环的数据类型为位置实参并赋值给溢出的位置形参,(打碎),例1
2
3
4
5
6def func(x, y, z):
print(x, y,z)
l = [1, 2, 3]
func(*l) # x, y, z = l # func(1, 2, 3)在形参与实参均存在
*
,(先打碎后封装),例1
2
3
4
5
6def func(x, y, *args): # args = ([3, 4, 5],) # args = (3, 4, 5)
print(x, y, args) # 1, 2, ([3, 4, 5], ) # 1, 2, (3, 4, 5)
func(1, 2, [3, 4, 5])
func(1, 2, *[3, 4, 5]) # func(1, 2, 3, 4, 5)
可变长度的**kwargs
参数:
**
保存溢出的关键字实参为字典类型并赋值给**
后面的参数名,规范使用kwargs
作为变量名,(封装),例 用户数据**
拆分**
后面的字典类型为关键字实参并赋值给溢出的默认形参,(打碎),例1
2
3
4
5
6
7def func(x, y, z):
print(x, y,z)
l = {'x': 1, 'y': 2, 'z': 3} # 字典`:`
func(*l) # x, y, z = l.key
func(**l) # x, y, z = l.value在形参与实参均存在
**
(先打碎后封装)
可变长度的位置参数与可变长度的关键字参数混用:** *args
必须在*kwargs
之前**
可变参数的骚操作: (先封装后打碎),index
直接获得wrapper
传入的实参
1
2
3
4
5
6
7
8
9 def index(x, y, z): # x = 1, y = 2, z = 3
print(x, y, z)
def wrapper(*args, **kwargs): # args = (1, 2) kwargs = {'y': 1,} # 封装
index(*args, **kwargs) # x, y = args, z = kwargs.value # 打碎
wrapper(1, 2, z=3)
命名关键字参数: 在*
后加形参,则该形参必须以关键字实参的形式传入
1
2
3
4
5
6 def func(x, y, *, a, b): # a,b为命名关键字参数
print(x, y)
print(a, b)
func(1, 2, a = 3, b = 4)
组合使用:
- 位置形参,默认形参,
*args
,命名关键字形参,**kwargs
- 位置实参,
*args
,关键字实参,**kwargs
总结
python中所有的传递都是内存地址的传递,即引用传递
默认形参的赋值不使用可变类型,应赋值为
None
,然后在函数体内定义为可变类型1
2
3
4
5
6
7def dunc(x, y, z, l = None)
if l is None:
l = []
l.append(x)
l.append(y)
l.append(z)
print(l)- (先封装再打碎), 获得原值
- (先打碎再封装),将可被for循环的数据类型转为元组,将字典转为字典
- 在这个过程中,填充形参与实参缺失的部分,使其一一对应.
- 位置形参,默认形参,
*args
,命名关键字形参,**kwargs
- 位置实参,
*args
,关键字实参,**kwargs
- 位置形参,默认形参,
名称空间与作用域
名称空间
名称空间: 存放名字,是对栈区的划分
好处: 可以在栈区存放相同的名字
名称空间分类
内置名称空间: 存放在python解释器内置的名字,例<built-in function print>
- 存活时间: python解释器的运行时间
全局名称空间: 非内置或函数内,剩下的都是, 包括if
和for
- 存活时间: python文件的运行时间
局部名称空间: 函数体内
- 存活时间:** 函数调用**时间
名称空间的加载顺序:** 内置**名称空间>全局名称空间>>局部名称空间(不一定加载)
名称空间的查找顺序:
- 函数体内: 局部名称空间>全局名称空间>内置名称空间
- 全局名称空间>内置名称空间
作用域
全局作用域: 全局存活,全局有效
局部作用域: 临时存活,局部有效
global: 在局部修改全局的名字对应的值(不可变类型)
1
2
3
4
5
6
7
8 def func():
global x
x = 222
x = 111
func()
print(x)
nonlocal: 在局部修改外层函数的名字对应的值(不可变类型)
1
2
3
4
5
6
7
8
9
10 def a():
x = 1
def b():
nonlocal x
x = 2
x =0
a()
print(x)
总结
解释器在函数体内(内置名称空间)找不到名称时,将在定义该函数体的全局名称空间或局部名称空间找,且以调用函数时的名称空间内数据为准.
函数定义时的位置决定是哪个名称空间,调用的位置决定名称空间里的数据
1
2
3
4
5
6def func():
print(x)
x = 1
func() # x=1而不是报错,此时名称空间内有x=11
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21def a():
y = z
print(x)
def b():
print(y)
b()
def c():
x = 3 # 提示未引用
y = 4 # 提示未引用
a()
x = 1
y = 2
z = 5
c() # x=1,y=5
print(x, y)解析:定义阶段
内置名称空间
print()=>内存地址
全局名称空间
- a()=>内存地址
- c()=>内存地址
- x=>1
- y=>2
- z=>5
函数a()
- y=>z
- b()=>内存地址
函数b()
None
函数c()
x=>3
y=>2
z=>5
运行阶段
c()=>a()=>新建局部名称y=z=>z=(局部=>上一级-全局名称空间=>z=5)5=>print(x=(局部=>上一级-全局名称空间x=1)1)=>b()=>print(y=(局部=>上一级-局部名称空间y=5)5)=>结束
结果
x=1,y=5
LEGB
- L —— Local(function);函数内的名字空间
- E —— Enclosing function locals;外部嵌套函数的名字空间(例如closure)
- G —— Global(module);函数定义所在模块(文件)的名字空间
- B —— Built_in(Python);Python内置模块的名字空间
函数对象与闭包函数
函数对象
函数对象: 函数可以被当做’数据’来处理
- 函数可以被引用
- 函数可以作为参数传入另外一个函数
- 函数的返回值可以是一个函数
- 函数可以作为容器类型的元素
1
2
3
4
5
6
7
8
9 def foo(x): # x = func, 函数可以被引用
return x # 函数的返回值可以是一个函数
def func():
pass
res = [foo(func),] # 函数可以作为参数传入另外一个函数
res[0]() # res = [func,], 函数可以作为容器类型的元素
函数嵌套
- 函数的嵌套调用:在函数内调用其他的函数
- 函数的嵌套定义:在函数内定义其他的函数
闭包函数
闭包函数=名称空间与作用域+函数嵌套+函数对象
核心点:函数定义时的位置决定是哪个名称空间,调用的位置决定名称空间里的数据
闭包函数: 若内嵌函数包含对外部函数作用域(而非全局作用域)中变量的引用,那么该’内嵌函数’就是闭包函数,简称闭包(Closures)
- “闭”代表是内嵌函数
- “包”代表函数外’包裹’着对外层作用域(而非全局作用域)的引用。
- 内嵌函数能够在全局使用
- 无论在何处调用闭包函数,使用的仍然是包裹在其外层的变量。
1
2
3
4
5
6
7
8
9 def f1():
x = 1 # 内嵌函数f2永远使用当前层变量(名称空间与作用域)
def f2(): # 内嵌函数f2(函数嵌套)
pass
return f2 # 在外部引用f2(函数对象)
f = f1()
f()
闭包函数的用途
为函数体传值的方式:
直接将值以参数的形式传入
将值包给函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24import requests
#方式一:
def get(url):
return requests.get(url).text
#方式二:
def page(url):
def get():
return requests.get(url).text
return get
# 方式一下载同一页面
get('https://www.python.org')
get('https://www.python.org')
get('https://www.python.org')
……
# 方式二下载同一页面
python=page('https://www.python.org')
python()
python()
python()
……
对比两种方式,方式一在下载同一页面时需要重复传入url,而方式二只需要传一次值,就会得到一个包含指定url的闭包函数,以后调用该闭包函数无需再传url
总结
python中所有的传递都是内存地址的传递,即引用传递
函数作为容器元素的应用: 批量管理函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16def login():
pass
def transfer():
pass
def add():
pass
func_dic={
'1'= login,
'2'= transfer,
'3'= add,
}
func_dic['1']() # login()- 函数对象的引用不加括号
- 闭包函数的精髓是:
- 返回的既可以是函数对象也可以是值
- 可以对原函数重新命名
- 可以为原函数提供新的参数,并提高原有参数的可拓展性
- 可以为原函数添加新功能而不改变调用方式