Python函数之自定义函数&作用域&闭包
一 前言
1.1 为什么要用函数
- 代码的组织结构更清晰,可读性好;
- 遇到重复的功能不需要重新编写代码,调用函数即可,代码不会冗余;
- 功能需要扩展时,只需要修改函数内容即可,实现统一管理,降低代码维护难度
函数式编程最重要的是增强代码的重用性和可读性
1.2 函数是什么
想象生活中的例子,修理工需要事先准备好工具箱里面放好锤子,扳手,钳子等工具,然后遇到锤钉子的场景,拿来锤子用就可以,而无需临时再制造一把锤子。
修理工--->程序员
具备某一功能的工具--->函数
要想使用工具,需要事先准备好,然后拿来就用且可以重复使用
要想用函数,需要先定义,再使用
1.3 函数的分类
内置函数
为了方便我们的开发,针对一些简单的功能,python解释器已经为我们定义好了的函数即内置函数。对于内置函数,我们可以拿来就用而无需事先定义,如len(),sum(),max()
自定义函数
很明显内置函数所能提供的功能是有限的,这就需要我们自己根据需求,事先定制好我们自己的函数来实现某种功能,以后,在遇到应用场景时,调用自定义的函数即可。
二 定义函数
2.1 如何自定义函数
语法如下:
def 函数名(参数1,参数2,参数3,...): # 参数可以没有
'''注释'''
函数体
return 返回的值 # 可以没有
# 函数名要能反映其意义
如何获取函数描述信息:函数名.__doc__
2.2 函数使用原则
函数即“变量”,“变量”必须先定义后引用。未定义而直接引用函数,就相当于在引用一个不存在的变量名。
函数的使用,必须遵循原则:先定义,后调用。我们在使用函数时,一定要明确地区分定义阶段和调用阶段。
# 定义阶段
def func1():
print('from func1')
func2()
def func2():
print('from func2')
# 调用阶段
func1()
2.3 函数在定义阶段做了什么
只检测语法,不执行代码。也就说,语法错误在函数定义阶段就会检测出来,而代码的逻辑错误只有在执行时才会知道
2.4 定义函数的三种形式
- 无参:应用场景仅仅只是执行一些操作,比如与用户交互,打印
- 有参:需要根据外部传进来的参数,才能执行相应的逻辑,比如统计长度,求最大值最小值
- 空函数:设计代码结构,无实际效果
# 定义阶段
def tell_tag(tag, n): # 有参数
print(tag * n)
def tell_msg(): # 无参数
print('hello world')
# 调用阶段
tell_tag('*',12)
tell_msg()
tell_tag('*',12)
# 执行结果
************
hello world
************
总结:
1. 定义时无参,意味着调用时也无需传入参数
2. 定义时有参,意味着调用时则必须传入参数
三 函数的调用
3.1 如何调用函数
函数名加括号,具体内部执行步骤为:
- 先找到名字
- 根据名字调用代码
3.2 函数的返回值
无return:没有返回值,打印为None
return 1个值:返回1个值
return 逗号分隔多个值:元组
return仅执行一次,意思就是说函数执行到return就终止该函数的继续执行
什么时候该有返回值? 调用函数,经过一系列的操作,最后要拿到一个明确的结果,则必须要有返回值 通常有参函数需要有返回值,输入参数,经过计算,得到一个最终的结果什么时候不需要有返回值? 调用函数,仅仅只是执行一系列的操作,最后不需要得到什么结果,则无需有返回值 通常无参函数不需要有返回值
函数的返回值可以是任意类型
3.3 函数调用的三种形式
- 语句形式:func()
- 表达式形式:3*func()
- 作为另外一个函数的参数:func2(func1())
补充知识:字符串、元组、列表、集合解压
a,b,c = '123'
print(a,b,c) # 1 2 3
a,b,c = (1,2,3)
print(a,b,c) # 1 2 3
a,b,c = [1,2,3]
print(a,b,c) # 1 2 3
a,b,c = {1,2,3}
print(a,b,c) # 1 2 3
a,_,c = '123'
print(a,c) # 1 3
四 函数的参数
4.1 形参与实参
形参:在函数定义阶段,括号内定义的参数-->形式参数-->本质就是变量名
实参:在函数调用阶段,括号内定义的参数-->实际参数-->实参可被当成变量值,必须要有明确的变量值。
形参即变量名,实参即变量值,函数调用时,将值绑定到变量名上,函数调用结束,解除绑定
4.2 形参、实参实际应用
位置参数:按照从左到右的顺序定义的函数
位置形参:必选必传值参数
位置实参:按照位置为形参传值
def func(a):
print(a)
func(1)
关键字参数:按照key=value的形式定义的实参
实参:无需按照位置为形参传值
主要注意的问题:
- 关键字实参必须在位置实参的后面
- 对同一个形参不能重复赋值
def func(a,b,c):
print(a,b,c)
func(1,b=2,c=3)
默认参数:形参在定义时就已经为其赋值
可以传值也可以不传值,经常需要变的参数定义成位置形参,变化较小的参数定义成默认参数(形参), 注意的问题:
- 只在定义时赋值一次
- 默认参数的定义应该在位置形参右面
- 默认参数通常应该定义成不可变类型
def func(a,b,c=3):
print(a,b,c)
func(1,b=2)
可变长参数:可变长指的是实参值的个数不固定,而形参有按位置和关键字两种形式定义,针对这两种形式的可变长,形参对应有两种解决方案来完整的存放他们,分别是*args,**kwargs。
# -------------*args--------------
def func1(x, y, *args):
print(x, y)
print(args) # 结果为元组(3, 4, 5)。args前可加*号,打印结果为3 4 5
func1(1,2,3,4,5)
def func2(x, y, *args):
print(x, y)
print(args) # 结果为元组(3, 4, 5)。args前可加*号,打印结果为3 4 5
func2(1,2,*(3,4,5))
def func3(x, y, z):
print(x, y, z) # 结果为:1 2 3
func3(*(1, 2, 3))
# -----------**kwargs------------
def func4(x, y, **kwargs):
print(x, y)
print(kwargs) # 结果为:{'c': 5, 'b': 4, 'a': 3}
func4(1,y=2,a=3,b=4,c=5) # 采用关键字形式赋值时注意顺序
def func5(x, y, **kwargs):
print(x, y)
print(kwargs) # 结果为:{'c': 5, 'b': 4, 'a': 3}
func5(1,y=2,**{'a':3,'b':4,'c':5})
def func6(x, y, z):
print(x, y, z) # 结果为:2 3 1
func6(**{'z':1,'x':2,'y':3})
# ---------*args + ** kwargs ----------
def func7(x, y):
print(x, y) # 结果为:(1, 2) {'a': 3, 'c': 5, 'b': 4}
def func8(*args, **kwargs):
func7(args, kwargs)
func8(1,2,a=3,b=4,c=5)
命名关键字参数:*后定义的参数,必须被传值(有默认值的除外),且必须按照关键字实参的形式传递。
可以保证,传入的参数中一定包含某些关键字
def func(x, y, *args, a=1, b, **kwargs):
print(x, y) # 1 2
print(args) # (3, 4, 5
print(a) # 1
print(b) # 1
print(kwargs) # {'d': 5, 'c': 4}
func(1, 2, 3, 4, 5, b=3, c=4, d=5)
五 函数嵌套
5.1 函数的嵌套调用
def max(x,y):
return x if x > y else y
def max_value(a,b,c,d):
res1 = max(a, b)
res2 = max(res1, c)
res3 = max(res2, d)
return res3
print(max_value(2,5,3,4))
5.2 函数的嵌套定义
def func1():
def func2():
def func3():
print('from f3')
func3()
func2()
func1()
func2() # 报错,为何?请看下一小节
func3() # 报错,为何?请看下一小节
六 名称(命名)空间与作用域
6.1 什么是名称空间
存放名字的地方,三种名称空间:
- builtins:内置名称空间(内置模块的名称空间)
- globals:全局名称空间(函数定义所在模块的名称空间)
- locals:局部名称空间(函数内的名称空间,包括局部变量和形参)
举个列子:a=1,1存放于内存中,那名字a存放在哪里呢?名称空间正是存放名字a与1绑定关系的地方
6.2 名称空间的加载顺序
加入我们执行python test.py,加载顺序如下:
1. python解释器先启动,因而首先加载的是:内置名称空间
2. 执行test.py文件,然后以文件为基础,加载全局名称空间
3. 在执行文件的过程中如果调用函数,则临时产生局部名称空间
6.3 作用域
全局作用域:全局存活,全局有效。内置名称空间与全局名称空间属于该范围
局部作用域:临时存活,局部有效。局部名称空间属于该范围
注意:查看作用域:globals():查看全局作用域;locals():查看当层作用域
6.4 名字的查找顺序
LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> __builtins__
locals:是函数内的名字空间,包括局部变量和形参
enclosing:外部嵌套函数的名字空间(闭包中常见)
globals:全局变量,函数定义所在模块的名字空间
builtins:内置模块的名称空间
a = 1 def func1(): # a = 2 def func2(): # a = 3 print(a) # 向上依次寻找 func2() func1() print(a) # 结果为1,无法访问局部,只能访问全局
x = 1 def func1(): def func2(): x = 99 print(locals()) # {'x': 99} print(x) # 99 return func2 x=100 def func3(func): x = 2 func() x=10000 func3(func1()) print(globals())
6.5 global与nonlocal关键字
global:适用于函数内部修改全局变量的值
nonlocal:适用于嵌套函数中内部函数修改外部变量的值
x = 1
def func():
global x
x = 2
func()
print(x) # 输出 2,无global声明则为1
如果不使用global关键字,python会在func函数这个本地作用域创建一个变量x,也就是说这个x只存在于本地作用域,并非是全局变量中的那个x。使用global关键字就是在告诉python,我们要在局部作用域使用全局变量,所以在x=2语句当中,python不会再在本地作用域中再新建一个变量,而是直接使用全局变量中的x变量。
def func1():
x = 1
def func2():
nonlocal x
x = 2
func2()
print(x)
func1() # 输出 2
如果不使用nonlocal,在嵌套的func2函数中,对变量x的赋值,python会在func2函数的内存空间在创建一个新的变量x。使用了nonlocal关键字就是告诉python,我们要在func2函数中使用外部变量x,所以我们在func2函数内对x变量进行赋值时就使用的是外部变量x,而不是重新创建出来的变量
注意:使用global关键字修饰的变量之前可以并不存在,而使用nonlocal关键字修饰的变量在嵌套作用域中必须已经存在,否则会报错。
七 函数对象与闭包
7.1 函数对象
函数是第一类对象,即函数可以当作数据传递
- 可以被引用(比如:内嵌函数)
- 可以当作参数传递(比如:函数返回值)
- 返回值可以是函数(闭包)
- 可以当作容器类型的元素
利用这一特性,我们可以简单的取代多分支的if
def func1(): print('func2') def func2(): print('func2') def func3(): print('func3') dic={ 'func1': func1, 'func2': func2, 'func3': func3, } while True: choice = input('>>: ').strip() if choice in dic: dic[choice]()
我们如果将函数对象赋值给变量,通过变量也能调用该函数。函数对象有一个__name__属性,可以拿到函数的名字:
def func():
pass
f = func
print(func.__name__) # func
print(f.__name__) # func
7.2 闭包
如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。
闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域。
在Python中创建一个闭包可以归结为以下三点:
- 闭包函数必须有内嵌函数
- 内嵌函数需要引用该嵌套函数上一级namespace中的变量
- 闭包函数必须返回内嵌函数
def func1():
x = 100
def func2():
print(x)
return func2
f = func1() # f-->内部的func2
f() # 执行func2()函数
print(f.__closure__[0].cell_contents) # 查看闭包的元素
实例应用--获取网页信息
from urllib.request import urlopen
def index(url):
def get():
return urlopen(url).read()
return get
baidu = index('http://www.baidu.com')
print(baidu().decode('utf-8'))


浙公网安备 33010602011771号