Python的函数、参数、作用域、闭包
主要关于:Python两种函数及参数类型:实参和形参、位置参数和关键字参数 以及变量作用域和闭包closure
一、函数类型
1. 普通函数
def square(x): # 关键字 函数名 输入参数
s = x*x # 函数主体
return s # 返回参数,可同时返回多个
2. lamada函数
func1 = lambda x:x+1 # 函数名 = 关键字 输入参数 返回参数
二、参数类型
1. 两种分类
-
实参和形参
-
实参:调用参数时用的参数: f(2)
-
形参:定义函数时,用的参数:
def f(a): # a是形参 return a f(2) # 2是实参
-
-
位置参数和关键字参数
2. 位置参数(传入时注重顺序)
-
定义
- 调用时,不需要指定参数,按顺序传入即可
- 调用时,也可以使用同名参数名传入
- 也可以有默认值,调用时既可以用
-
分类
传参顺序:必须参数,默认参数、可变长位置参数
-
必须参数:只有形参
def f(x,y):pass
-
默认参数:
-
定义:有形参有默认值
-
使用:4种方法
def f(x,y=1,z=2):pass # 必须参数x, 默认参数y,z f(1) #1 可以不传默认参数 f(1,3,4) #2 按顺序传入三个参数 f(1,z=1,y=1) #3 默认参数的可以不按参数顺序传入 f(*[1,3,4]) #4 一次传入多个,用*解包成多个元素
在没有斜杠的时候,调用位置参数时,既可以用位置也关键字方法
def f(pos1,pos2,/,pos_kw,*,kw1,kw2):
-
-
可变长的位置参数
-
定义:参数的个数不定
-
使用:2种方法
def f(x,*args): # 通常用*args表示不定长位置参数 print(type(args),args) # tuple f(1,2,3,4,5) #1 直接传入 f(1,*[1,3,4]) #2 一次传入多个,用*解包成多个元素
-
打包(pack)和解包(unpack):
- 函数定义时, * 可以将按位置将传递进来的参数 打包成元组 (tuple) 类型;
- 函数调用时, *可以解包待传递到函数中的元组、 列表、集合、字符串等类型, 并按位置传递到函数
- 函数调用时,**可以解包字典,并按照关键字传递到函数入口参数中(下面会用到)
-
3. 关键字参数(传入时提供关键字)
-
定义
-
调用时需要提供关键字的名字,才能传入参数
-
关键字参数,可以不按照顺序输入
关键字参数和位置参数混用时,需要位置参数在前
-
-
分类
-
类似位置参数中的必须参数
-
定义:在调用时,需要用关键字=value才可以使用
-
使用:
- 直接传入:f(x=1,y=2)
- 一次传入多个参数:用**解包,来传入f(**{‘x’:1,‘y’:2})
def f1(x,y,z):pass f1(x=1,y=2,z=3) #f1(x=1,3,z=3) # 关键字参数要在位置参数前,x=1之后,都是关键字参数,所以3需要带上关键字 f1(**{ 'x':1,'y':2,'z':3}) # 用**解包字典,当成关键字传入函数
-
-
可变长的关键字参数
- 定义:参数个数不定
- 使用:同上
def f2(**kwargs):print(type(kwargs),kwargs) # dict def f2(x,**kwargs):print(type(kwargs),kwargs) # dict f2(x=1,y=2,z=3) f2(**{ 'x':1,'y':2,'z':3})
-
命名的关键字参数
-
定义:可以指定变量名的关键字参数,调用时需要加变量名
-
使用:
def f3(x,*,y=2,z):pass # 命名的关键字参数,*表示后面的参数都是关键字参数, def f4(x,*args,y,z):pass #如果关键字参数前有可变长位置参数,则不需要再加* 了 f3(x=1,y=2,z=3) f3(**{ 'x':1,'y':2,'z':3}) f4(1,2,3,4,y=3,z=4) # f4(1,2,3,4,3,4) # 若不指定y和z的取值,就会报错,因为y,z是关键参数
-
-
4. 位置参数、关键字参数的混用
- 位置参数中,必须参数在前,默认参数在中,可变长参数在后
- 关键字参数中,命名参数在前,不定长参数在后
- 位置和关键字混用时,位置参数在前,关键字参数在后
- f(*args,**kwargs)可以传入所以类型的参数
一个压箱底的例子
def f(x,y=2,*args,k1,k2=1,**kwargs):
print("位置参数有:必须参数、默认参数、可变长参数:")
print("\t必须参数是:x =",x)
print("\t默认参数是:y = ",y)
print("\t可变长参数是:args =",args)
print("关键字参数有:命名关键字参数、可变长关键字参数:")
print("\t命名关键字参数是:k1 = %d, k2 = %d"%(k1,k2))
print("\t可变长关键字参数是:kwargs =",kwargs)
f(1,2,*(3,4,5),k1=6,k2=7,**{
'a':1,'b':2,'c':3})
python 语言中实现C的三目运算符:
# bSum?x+y:x-y # 这个return有点儿东西的啊 def f(x,y,bsum=True): return x+y if bSum else x-y f(1,2,1)
三、变量作用域(LEGB原则)
1. LEGB原则:
- 当一个函数体内需要引用一个变量的时候, 会按照如下顺序查找:
- Locals: 首先查找局部变量
- Enclosing function locals: 去函数体的外层去寻找局部变量(嵌套情况)
- Gloal:从全局变量中寻找
- Built-in:还是找不到, 只好去找内置库 .
2. global 和 non local
global :声明把该变量变成全局变量
nonlocal : 声明局部变量的作用域,向外层扩展
When we declare a variable inside a nested function as nonlocal, its scope is extended beyond this inner-function to the outer-function it is nested within.
def outer_f():
a = 'outer_string'
def inner_f():
nonlocal a # 扩展变量a的作用域到外层
a = 'local_string'
global b # 扩展变量b的作用域到全局
b = 'global_string'
inner_f();print(a) # local_string
outer_f();print(b) # global_string
函数内,调用全局变量可以,但是不能重赋值全局变量
a = 0
def addtwo():
c =a+2 # 此时可以,可以认为是调用全局变量a
a = a+2 # return a 此时会报错,因为此时a赋值就变成了局部变量
return c
d = addtwo()
四、closure闭包:(算是一种轻类)
1. 闭包的定义
把数据和函数绑定在一起的技术叫闭包。函数和函数调用的外部变量合在一起称为一个closure。
通俗的说,一个函数内部没有定义该变量,但是其外层函数定义了,假如内层调用的话,则该变量就会绑定binding到内层函数上,这叫闭包。
2. 闭包的准则
- 必须是一个嵌套函数
- 被嵌套的函数,一定要调用包裹函数内的变量
- 包裹函数,必须返回被嵌套的函数(类似装饰器)
def out_f(): #1
a = 'out_string'
def inner_f(): #1
print(a) #2
return inner_f #3
f = out_f() # 返回的是一个函数
f()
3. 闭包的特点
- 该数据不是通过参数传进去的
- 在闭包区域(enclosing)的变量和内层函数会被保存,即使变量跑出闭包区域或者外层函数被删除了,变量依然在。
- 所有的函数都有一个__closure__属性
- 如果函数是一个闭包,则其__closure__属性就会返回一个tuple of cell objects
- _closure_[0].cellcontents 可以查看闭包里存了哪些变量
out_f.__closure__[0].cellcontents # 'out_string'
dir();del out_f;dir() # 闭包函数out_f被删了,out_f中第2行到第4行的对象也会被保存
f.__closure__[0].cellcontents # 'out_string'
4. 什么时候使用闭包closure
- 闭包可以避免使用全局变量,可以隐藏一些变量
- 可以包含一些属性和多个函数(通常是一个,返回一个函数元组),算是一种轻类,更简洁。
- 有时装饰器算是一种闭包(闭包第2个特征有时可能不满足)
def out_f():
a = 'out_string'
def inner_f():print(a)
def inner_f1():print(a*2)
return inner_f,inner_f1
f = out_f()
f[0]();f[1]() # 分别调用两个函数