week3 day2函数高级用法
一. 函数对象
函数和变量有相通之处。变量的使用原则是先定义后引用,函数的使用原则是先定义后使用。变量名绑定了变量值对应的内存空间的内存地址。函数名绑定了函数体代码对应的内存空间的内存地址。因此,适用于变量名使用的场景也适用于函数名的使用场景。
- 赋值
变量 a=10 b=a
函数 def func(): print(1) f=func print(f)----->func的内存地址 f()----->1
- 传参
变量 a=10 def func(x): ... func(a)
函数 def index(): ... def func(m): ... func(index)
- 返回值
变量 a=10 def func(): return a
函数 def index(): ... def func(): return index res=func() res()
- 在容器型数据结构中按照索引(key)取到
变量 a=10 list1=[a] list1[0]
函数 def func(): ... list1=[func] list1[0]()
第四种函数对象的使用方法可以让我们之前写过的atm功能更加简洁:
def login():
...
def register():
...
def withdraw():
...
def deposit():
...
def transfer():
...
func_info={
'0':['退出',exit],
'1':['登录',login],
'2':['提现',register],
'3':['存钱',withdraw],
'4':['转帐',transfer]
}
while True:
for key,value in func_info:
print(key,value[0])
choice=input('请输入您想执行的操作: ').strip()
if choice in func_info():
print('您正在执行 {} 操作'.format(func_info[choice][0]))
func_info[choice][-1]()
二. 函数嵌套
2.1 函数的嵌套定义
def func():
x=10
def sub_func():
print(1)
func()----->None
def func():
x=10
def sub_func():
print(1)
sub_func()
func()----->1
2.2 函数的嵌套调用
比较输入的多个值的最大值的函数
def max2(x,y):
if x>y:
return x
else:
return y
def max_many(*args):
list1=list(args)
res1=list1[0]
if len(list1)>=2:
for i in range(1,len(list1)):
res1=max2(res1,list1[i])
print(res1)
三. 名称空间与作用域
3.1 名称空间
名称空间namespaces。名称空间即存放名字与对象映射/绑定关系的地方。对于x=3,Python会申请内存空间存放对象3,然后将名字x与3的绑定关系存放于名称空间中,del x表示清除该绑定关系。在程序执行期间最多会存在三种名称空间,分别为:内置名称空间,全局名称空间,局部名称空间。
名称空间与作用域在定义阶段就确定了,而不是调用阶段
名称空间与作用域在定义阶段就确定了,而不是调用阶段
名称空间与作用域在定义阶段就确定了,而不是调用阶段
名称空间种类 | 定义 | 生命周期 |
---|---|---|
内置名称空间 | 存放内置的名字 | python解释器调用产生—>python解释器关闭销毁 |
全局名称空间 | 存放的是顶级的名字 | python文件运行产生—>python文件停止运行销毁 |
局部名称空间 | 存放的是函数内的名字 | 调用函数时产生,调用结束销毁 |
三种名称空间没有任何相交关系,但为了方便理解,可以理解为三层结构。内置名称空间在最外层,内层包含若干个(和python文件个数相同)全局名称空间,全局名称空间内层包含若干个(和python文件中调用函数的个数相同)函数的局部名称空间。
名称空间的加载顺序是:内置名称空间->全局名称空间->局部名称空间,而查找一个名字,必须从三个名称空间之一找到,查找顺序为:局部名称空间->全局名称空间->内置名称空间。
即LEGB层次。local----->enclosing functions----->global----->built-in functions.
案例一:
def f1():
x=555
def f2():
x=666
print(x)
f2()
x=444
f1() # ----->666 从f2的局部名称空间找到的
def f1():
x=555
def f2():
print(x)
f2()
x=444
f1() # ----->555 从f1的局部名称空间中找到的
def f1():
def f2():
print(x)
f2()
x=444
f1() # ----->444 从全局名称空间中找到的
案例二:
len=10
def func():
len=2
print(len)
func() # ----->2 从局部名称空间中找到的
len=10
def func():
print(len)
func() # ----->10 从全局名称空间中找到的
def func():
print(len)
func() # ----->len 从内置名称空间中找到的内置函数
案例三:名称空间与作用域只与定义阶段有关,与调用无关
x=111
def f1():
print(x)
def f2():
x=222
f1()
f2() # ----->111 f1在全局名称空间中,没在f2的局部名称空间中
案例四:
x=111
def f1():
print(x) # local variable 'x' referenced before assignment
x=222
f1()
print(y) # name 'y' is not defined
案例五:
x=111
def func():
print(x)
x=222
func() # ---> local variable 'x' referenced before assignment
确实在局部名称空间中找到了变量x,但是变量x只有运行到print后的那行代码才能确定x的值,因此在打印变量x的时候,局部名称空间中的x还未定义
3.2 作用域
- 全局作用域:内置名称空间+全局名称空间
特点:全局存活,全局有效x=10 def func(): global x # 在函数内,无论嵌套多少层,都可以查看到全局作用域的名字,若要在函数内修改全局名称空间中名字的值,当值为不可变类型时,则需要用到global关键字 # 此时修改实参的值为不可变类型,当修改可变类型数据的值时,函数体内对该值的修改将直接反应到原值,无需声明global x=20 print('before func:',x)----->10 func() print('after func:',x)----->20
- 局部作用域:局部名称空间
特点:局部存活,局部有效x=10 def index(): x=20 def func(): nonlocal x # 对于嵌套多层函数,使用nonlocal关键字可以将名子声明为来自外部嵌套函数定义的作用域(非全局) x=10 print('before func:',x) func() print('after func:',x) index()----->20 ----->10
3.3 作用域与名字查找的优先级
在局部作用域查找名字时,起始位置是局部作用域,所以先查找局部作用域,没有找到,再去全局作用域查找:先查找全局名称空间,没有找到,再查找内置名称空间,最后都没有找到就会抛出异常。
x=100 #全局作用域的名字x
def foo():
x=300 #局部作用域的名字x
print(x) #在局部找x
foo()#结果为300
在全局作用域查找名字时,起始位置便是全局作用域,所以先查找全局名称空间,没有找到,再查找内置名称空间,最后都没有找到就会抛出异常。
x=100
def foo():
x=300 #在函数调用时产生局部作用域的名字x
foo()
print(x) #在全局找x,结果为100
四. 函数对象
函数对象指的是函数可以被当作“数据”来处理,具体可以分为四个方面的使用。
4.1 函数可以被引用
deff add(x,y):
return x+y
func=add
func(1,2) # ---> 3
4.2 函数可以作为容器类型的元素
dic={'add':add,'max':max}
print(dic) # ---> {'add': <function add at 0x100661e18>, 'max': <built-in function max>}
dic['add'](1,2) # ---> 3
4.3 函数可以作为参数传入另外一个函数
def foo(x,y,func):
return func(x,y)
foo(1,2,add) # ---> 3
4.4 函数的返回值可以是一个函数
def bar():
return add
func=bar()
func(1,2) # --->3
五. 闭包函数
5.1 闭包概念
基于函数对象的概念,可以将函数返回到任何位置去调用,但作用域的关系是定义完函数时就已经被确定了的,与函数的调用位置无关。
闭:定义在函数内的函数
包:该函数引用了一个外层函数作用域的名字
闭包=函数的名称空间与作用域+函数对象+函数的嵌套定义
def outter():
x=10
def wrapper():
print(x)
return wrapper
可以通过函数的closure属性,查看到闭包函数所包裹的外部变量。闭包函数其实就是另一种给函数传参的方式。
5.2 为函数体传参
为函数传参的两种方式:
- 利用实参为形参传值
def wrapper(x,y): print(x+y) wrapper(1,2)----->3
- 闭包函数传值
def outter(x,y): def wrapper(): print(x+y) return wrapper f=outter(3,4) f() # --->7
六. 装饰器
6.1 什么是装饰器?
装饰器就是为被装饰对象添加新功能的工具。
6.2 为何要用装饰器?
软件的设计应该遵循开放封闭原则,即对外扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。
软件包含的所有功能的源代码以及调用方式,都应该可以避免,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的产品,
开放封闭原则:
开放:对扩展新功能开放
封闭:对修改原代码封闭
原则:
1.不修改原函数的原代码
2.不改变原函数的调用方法
6.3 怎么使用装饰器?
无参装饰器模板
def outter(func):
def wrapper(*args,**kwargs):
# 为其增加的新功能
res=func(*args,**kwargs)
# 为其增加的新功能
return res
return wrapper
有参装饰器模板
def chuancan(args=xyz): # 直接传参
def outter(func):
def wrapper(*args,**kwargs):
# 为其增加的新功能
res=func(*args,**kwargs)
# 为其增加的新功能
return res
return wrapper
return outter
了解:将原函数的所有属性都转移给新函数,即保留原函数的所有属性。functools模块下提供一个装饰器wraps专门用来帮我们实现这件事,用法如下
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
return wrapper
6.4 语法糖
为了是使用装饰器的时候调用更加简便,引入了语法糖的概念。
import time
def outter(func):
def wrapper(*args,**kwargs):
start=time.time()
res=func(*args,**kwargs)
end=time.time()
print(end-start)
return res
return wrapper
@outter # index=outter(index)
def index():
time.sleep(1)
print(123)
index()----->123 1.0028467
6.5 叠加多个装饰器的运算规则
def deco1(func1): # func1=wrapper2
def wrapper1(*args,**kwargs):
print(111)
res1=func1(*args,**kwargs)
print('end1')
return res1
return wrapper1
def deco2(func2): # func2=wrapper3
def wrapper2(*agrs,**kwargs):
print(222)
res2=func2(*args,**kwargs)
print('end2')
return res2
return wrapper2
def deco3(func3): # func3=index
def wrapper3(*args,**kwargs):
print(333)
res3=func3(*args,**kwargs)
print('end3')
return res3
return wrapper3
@deco1 # deco1(wrapper2)--->return --->wrapper1
@deco2 # deco2(wrapper3)--->return--->wrapper2
@deco3 # deco3(index)--->return--->wrapper3
def index():
print('from index')
index()----->111
222
333
from index
end3
end2
end1
装饰器只需要看最内层的函数体代码就可以了,多个装饰器嵌套执行顺序从上到下
装饰器只需要看最内层的函数体代码就可以了,多个装饰器嵌套执行顺序从上到下
装饰器只需要看最内层的函数体代码就可以了,多个装饰器嵌套执行顺序从上到下
上面例子的具体原理图可以看下图: