python基础九 函数(中)作用域、命名空间、递归函数
1. 文档字符串
- 查看帮助文档__doc__和help()函数
- __doc__查看帮助文档
#吃榴莲的过程
def func():
"""
功能:这是一个吃榴莲的过程
参数:
返回值:
"""
print("拿出榴莲")
print("榴莲皮去掉")
print("果肉放进嘴里")
return "太香了"
func()
#用__doc__查看func这个函数的帮助文档
print(func.__doc__)
- help()是Python中内置函数,通过help()函数可以查询Python中函数的用法
# help()函数用法:help(函数名)
help(input) #直接运行,就会调用出input这个函数的用法
- 在定义函数时,可以在函数内部编写文档字符串,文档字符串就是对函数的说明
def fun(a, b, c):
"""
这是一个文档字符串的示例
这个函数是做什么用的。。。。
:param a: 作用 类型 默认值
:param b: 作用 类型 默认值
:param c: 作用 类型 默认值
:return: 需不需要返回值
"""
return 123
help(fun)#查看fun这个函数的用法
2. 函数的作用域
2.1 全局作用域
- 概念
全局作用域在程序执行时创建,在程序执行结束时销毁;所有函数以外的区域都是全局作用域;在全局作用域中定义的变量,都是全局变量,全局变量可以在程序的任意位置进行访问 - 在函数外部定义的变量
a = 123
def fun():
b = 456
print('函数内部a = ',a)
print('函数内部b =',b)
fun()
print('函数外部a = ',a) # 输出结果为123,变量a是定义在函数外面,所以可以在任何地方使用,a就是全局变量
print('函数外部b = ',b) # 运行时会报错,因为b是函数内部的变量,不能再函数外面使用。
- 在函数内部的变量,但是变量前面+global,此时函数的局部变量就会变成全局变量。
def func():
global a #用global声明a是全局变量
a = 123
b = 456
print('函数内部a = ',a) # 在函数内部可以正常输出
print('函数内部b =',b)
func()
print('函数外部a = ',a) # 在函数外部也可以正常输出,因为global a,在函数内部声明a为全局变量,所以在任何地方都可以访问
print('函数外部b = ',b) # 但是b是在函数内部的变量,并没有声明b为全局变量,所以在函数外部,是不能访问到b。打印时会报错。
-
global总结
- 如果函数外部有这个全局变量,在函数内部使用global关键字,可以修改全局变量。
a = 1000 def fun1(): global a # 函数内给全局变量使用global,可以修改全局变量 a = 2000 # 重新给全局变量赋值 print("a修改后在函数内部",a) fun1() print("a修改后在函数外部",a) #输出结果: a修改后在函数内部 2000 a修改后在函数外部 2000 #在函数内部可以直接获取全局变量,但是无法直接修改全局变量,需要通过global修改。 f = 101 def fun2(): f = 102 print(f) # 这会报错,在函数内部可以直接获取全局变量,但是无法直接修改全局变量,需要通过global修改,用global f ,才能f=102 fun2() print(f)
- 如果函数外部没有这个全局变量,在函数内部使用global关键字,可以定义全局变量。
2.2 函数内部作用域
- 概念
函数作用域在函数调用时创建,在调用结束时销毁;函数每调用一次就会产生一个新的函数作用域;在函数作用域中定义的变量,都是局部变量,它只能在函数内部被访问。
a = 123
def fun1():
b = 456
print("a全局变量",a)
print("b局部变量",b)
fun1()
print("a全局变量",a)
print("b局部变量",b)#会报错,因为b是函数局部作用域里设定的变量,称之为局部变量,函数外部是不可以访问的。
3. 函数名的使用(重点)
- python中的函数可以像变量一样,动态创建、销毁、当参数传递、作为返回值,叫做第一类对象,其他语言 不能比拟功能有限。
- 函数名是一个特殊的变量,可以当做变量赋值。
def func1():
print("我是func1")
return 111
res = func1()
print(res)
# 动态创建函数
func = func1
func()
# 动态销毁函数
del func
func()
- 函数名可以作为容器类型数据的元素
def func2():
print("我是func2")
def func3():
print("我是func3")
def func4():
print("我是func4")
return "func4"
lst = [func2,func3,func4] #将函数名当做列表的的元素
for i in lst:#遍历列表
i() #调用函数
- 函数名可以作为函数的参数
#函数名作为函数的参数
def myfunc(f): # f形参
res = f() # 形参f是一个函数,既然是一个函数,那么就可以调用,f()
print(res)
# 此刻f <==> func4 <==> res = func4() print(res)
myfunc(func4)
- 函数名可以作为函数的返回值
def myfunc2(f):
return f
f2 = myfunc2(func4) # f2 = func4
print(f2) # 打印显示f2 就是func4
f2() # 既然f2 = func4,那么看看可以不可以调用。
4. 命名空间
- 概念:命名空间实际上就是一个字典,是一个专门用来存储变量的字典。
- 生命周期:python内置关键字变量>全局变量>局部变量。
从内存角度分析:python内置变量编译器打开的时候就已经在电脑内存里开辟空间,直到编译器关闭,这一块内存才会释放,所以它的生命周期最长;其次是全局变量,当打开一个文件的时候,全局变量在内存中开辟一个空间,直到文件关闭它的内存才会释放;最后函数局部变量(短命鬼),创建函数的时候,内存开辟一个空间存储这个变量,函数调用完后,变量内存就会释放。 - locals和globals
- locals 获取当前作用域中的所有内容
- locals 如果在函数外,调用locals(),获取的是打印之前的所有变量,返回字典,全局空间作用域。
打印结果:a = 1 b = 2 res = locals() # 调用locals()函数 c = 3 print(res) d = 4
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000000001DBF7C8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'F:/software/LG_education/play_test/haha2.py', '__cached__': None, 'a': 1, 'b': 2, 'res': {...}, 'c': 3}
- locals 如果在函数内,调用locals(),获取的是调用之前的所有变量,返回字典,局部空间作用域。
打印结果:从结果看出,只获取了locals()函数调用之前变量ba = 1 def func(): b = 2 res = locals() # 调用locals()函数 c = 3 print(res) d =4 func()
{'b': 2}
- globals获取全局作用域的所有内容
- globals 如果在函数外,调用globals(),获取的是打印之前的所有变量,返回字典,全局空间作用域。
打印结果:a = 5 b = 6 res = globals() c = 7 print(res) d = 8
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000000000211F7C8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'F:/software/LG_education/play_test/haha2.py', '__cached__': None, 'a': 5, 'b': 6, 'res': {...}, 'c': 7}
- globals 如果在函数内,调用globals(),获取的是函数调用之前的所有变量,返回字典,全局空间作用域。
打印结果:从结果看出globals只获取全局作用域的变量,不会获取函数局部变量a = 10 def func1(): b = 11 c = 12 res = globals() d = 13 print(res) ff = 50 func1() zz = 100
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000000000068F7C8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'F:/software/LG_education/play_test/haha2.py', '__cached__': None, 'a': 10, 'func1': <function func1 at 0x0000000001E870D8>, 'ff': 50}
- globals 返回的是系统的字典
- 正常方式定义变量
zhangsan = '112233'
- 通过系统的全局字典添加键值对,可以动态创建全局变量
dic = globals()
print(dic)
# 传递字符串,创建一个变量。
k = "sunwukong"
dic[k] = "齐天大圣"
print(sunwukong)
# 打印结果:齐天大圣
- 批量创建全局变量,在函数中
打印结果:def func(): dic = globals() for i in range(1,6): dic[f'p{i}'] = i func() print(p1) print(p2) print(p3) print(p4) print(p5)
1 2 3 4 5
5. 递归函数
- 递归是解决问题的一种方式,它的整体思想,是将一个大问题分解为一个个的小问题,直到问题无法分解时,在去解决问题
- 递归式函数有2个条件:
- 基线条件 问题可以被分解为最小问题,当满足基线条件时,递归就不执行了
- 递归条件 可以将问题继续分解的条件
示例1:
# 递归函数的两个条件
# 1. 基线条件 问题可以被分解为最小的问题,当满足基线条件的时候,再去解决问题 (设定的最大递归深度)
# 2. 递归条件 将问题继续分解的条
# 求任意数n的阶乘
def func(n):
if n <= 1: # 基线条件
return 1
return n * func(n-1) # 递归条件
res = func(5)
print(res)
# 递归函数的两个条件
# 1. 基线条件 问题可以被分解为最小的问题,当满足基线条件的时候,再去解决问题 (设定的最大递归深度)
# 2. 递归条件 将问题继续分解的条件
# 10! n! fun(n) 是求n的阶乘的函数
# 10! = 10 * 9! n*(n-1)! n * fun(n-1) 求n-1的阶乘的函数
# 9! = 9 * 8!
#.....
# 2! = 2*1!
# 1! = 1
- 递归函数: 自己调用自己的函数是递归函数
递:去
归:回
一去一回叫递归def digui(n): print(n,"<===1===>") if n > 0: digui(n-1) print(n,"<===2===>") digui(5) """ 543210012345 去的过程: 当n=5时 print(5,"<===1===>") 5>0 digui(5-1) <=> digui(4) <=> 当前代码在第4行,代码暂停阻塞。 当n=4时 print(4,"<===1===>") 4>0 digui(4-1) <=> digui(3) <=> 当前代码在第4行,代码暂停阻塞。 当n=3时 print(3,"<===1===>") 3>0 digui(3-1) <=> digui(2) <=> 当前代码在第4行,代码暂停阻塞。 当n=2时 print(2,"<===1===>") 2>0 digui(2-1) <=> digui(1) <=> 当前代码在第4行,代码暂停阻塞。 当n=1时 print(1,"<===1===>") 1>0 digui(1-1) <=> digui(0) <=> 当前代码在第4行,代码暂停阻塞。 当n=0时 print(0,"<===1===>") 0>0 条件不满足,返回False,不执行调用,print(0,"<===2==>") 回的过程: n = 1 从阻塞位置第4行继续向下执行print(1,"<===2===>") n = 2 从阻塞位置第4行继续向下执行print(2,"<===2===>") n = 3 从阻塞位置第4行继续向下执行print(3,"<===2===>") n = 4 从阻塞位置第4行继续向下执行print(4,"<===2===>") n = 5 从阻塞位置第4行继续向下执行print(5,"<===2===>") 到此代码全部执行结束 543210012345 递归函数有回的过程,有两种情况可以触发: (1)当最后一层函数全部执行结束的时候,有触底反弹的过程(回马枪),回到上层函数空间的调用处。 (2)遇到return 返回值,直接返回上层空间调用处 递归: (1)取得过程就是不停的开辟栈帧空间,在回的时候,就是在不停的释放栈帧空间,递归函数就是不停的开辟和释放栈帧空间的一个完成的过程。 (2)回的时候有两种触发机制,要么是最后一层函数空间全部执行完毕,要么就是遇到return,都会触底反弹(回马枪) (3)写递归函数时候,必须给与跳出的条件,如果递归的层数过多,不推荐使用,容易内存溢出或者蓝屏。 (4)递归调用每一层空间都是独立的个体,独立的副本,资源不共享。 函数在运行的时候,需要内存开辟空间才可以,这个空间叫做栈帧空间。 """