day11-函数对象,函数嵌套,名称空间与作用域,闭包函数,以及装饰器的前言
一.函数对象
在python中:函数是第一类对象,函数是第一等公民
本质:函数可以当作变量用
1.可以赋值
def func():
print('from func')
f = func
print(f) # <function func at 0x0000017E65C12288>
f() # from fun
2.可以当作函数传给另外一个函数
def func():
print('from func')
def foo(x):
print(x) # <function func at 0x000002D284BB7EE8>
x() # from func
foo(func)
3.可以当作函数的返回值
def func():
print('from func')
def foo(x):
return x
res = foo(func)
print(res) # <function func at 0x000001E64BDAC048>
4.可以当作容器类型的元素
def func():
print('from func')
l = [func, ]
print(l) # [<function func at 0x000001E64BDAC048>]
l[0]() # from func
函数名当作容器类型的元素之字典的存放 案例:
def withdraw():
print('取款')
def transfer():
print('转账')
def check_balance():
print('查询余额')
def deposit():
print('存款')
func_dic = {
"0": ['退出', exit],
"1": ["提款", withdraw],
"2": ["转账", transfer],
"3": ["查询余额", check_balance],
"4": ["存款", deposit]
}
while True:
for k, v in func_dic.items():
print(k, v[0])
choice = input(">>>: ").strip()
if choice in func_dic:
func_dic[choice][1]()
else:
print("输入错误")
函数名当作容器类型的元素之列表的存放 案例:
def withdraw():
print('取款')
def transfer():
print('转账')
def check_balance():
print('查询余额')
def deposit():
print('存款')
func_list = [["退出", exit], ["取款", withdraw], ["转账", transfer], ["查询余额", check_balance], ["存款", deposit]]
while True:
for index1, func1 in enumerate(func_list):
print(f"{index1} 的功能为{func1[0]}")
chios = input(">>>>:").strip()
if not chios.isdigit(): continue
chios = int(chios)
for index, func in enumerate(func_list):
if chios == index:
func_list[chios][1]()
else:
print('暂不支持')
二.函数的嵌套
函数的嵌套定义: 在一个函数中定义了另外一个函数
例子:比较4个数的大小
def max2(a,b):
if a > b:
return a
else:
return b
def max4(a,b,c,d):
res = max2(a, b)
res1 = max2(res,c)
res2 = max2(res1,d)
return res2
print(max4(15,523,523,535))
例子:函数内的变量名在外部不能使用
def f1():
x = 111
def f2():
print('from f2')
print(x)
print(f2)
f1()
# print(x) # NameError: name 'x' is not defined
# print(f2) # NameError: name 'f2' is not defined
# 总结,在函数体内部定义的"变量"只能在自己的函数体内部,被引用,在函数体外则不能被引用
三.名称空间与作用域
名称空间
概念:namespaces名称空间,存放名字的地方,具体来说是名字和内存地址绑定关系的地方
内置名称空间: 存放内置的名字
生命周期:python解释器启动则删除,关闭则销毁
全局名称空间:除了在函数内的名称的名字
生命周期:运行python文件时则产生,python文件运行完毕则销毁
-
x = 1 if 1 > 0: x1 = 2 while True: x2 = 3 以上的xn都是全局名称 也都是顶级的
局部名称空间:存放的是函数内的名字
生命周期:执行函数内的代码则产生,执行完就销毁
核心:名字的访问优先级
基于当前所在的位置向外查找
函数内---> 外层函数 ---> 全局 ---> 内置
案例1:len
不同的名称空间的使用
len = 10
def func():
len = 20
print(len)
func() # 20
print(len) # 10
案例2:执行f1函数,x的值会怎么变化(将函数体代码x的注释打开,从里到外)
def f1():
# x = 555
def f2():
# x = 666
print(x)
f2()
x = 444
f1() # 在执行f1之前全局名称空间中就有x这个名称了
f1() # 在执行f1的时候,名称空间中还没有x这个名称,报错:NameError: name 'x' is not defined
x = 444
专业术语:LEGB : L ---> 内层函数; E-----> 外部函数; G-----> 全局名称,B---->内置名称
名称空间与作用域的关系是在函数定义阶段(扫描语法)就确立的,与什么时候调用以及调用位置无关
案例1:判断下列函数中x的值打印出来是什么?
x = 111
def f1():
print(x) # x只管在哪被定义的
x = 222
f1()
案例2:当在局部名称空间中改变全局名称空间的可变数据类型时
l = []
def func():
l.append(1111) # l表示的是全局名称空间的l
func()
print(l) # 打印出的全局名称空间的l为[1111]
上面在函数内即局部名称空间改变全局名称空间用的是.append功能,改变的是全局的,但你在局部给他重新赋值看看
l = []
def func():
l = [11,22,33,44]
func()
print(l) # []
你会发现并没有 给 全局的l重新附上 对应的 值,原因就是 局部的l=[11,22,33,44]是一个新的l,即是局部的l.
现在有一个需求.就是将全局名称空间的值是一个不可变的类型,我能怎么更改呢???
你可能会试着用 x = xxx 给他重新赋值 或者 字符串的操作,但是你会发现它都不能更改,因为是一个不可变的数据类型,所以python给我们提供了一个global
关键字,表示其后面的名称是全局的
案例:将x这个整数111改为222
x = 111
def func():
global x # 声明这里用的x是一个全局变量
x = 222
func()
print(x) # 222
而对应的也有一个关键字可以声明变量名是来自于外层函数的,不是全局的,这个关键字就是nonlocal
案例:
# nonlocal # 声明变量名是来自与外层函数的,不是全局
x = 111
def f1():
x = 222
def f2():
# global x # 声明变量名是来自于全局
nonlocal x # 声明变量名是来自于外层函数的,不是全局
x = 333
f2()
print(x)
f1() # 333
四.闭包函数=函数对象+函数嵌套+名称空间与作用域
闭包函数就是一个对上面学习的一个汇总.
闭:指的该函数是定义在函数内的函数
包:指的就是该函数引用了一个外层函数作用域的名字
例子1:执行了outter函数就是相当于执行了wrapper(),即在全局名称空间执行到了局部名称空间内的代码
def outter():
x = 111 # x表示的就是包
def wrapper(): # wrapper表示就是闭
print(x)
return wrapper
f = outter()
print(f) # 111
闭包函数其实本质就是为函数体代码传参的一种方式
我们之前都学过,为函数体代码传参最直接的方式就是用参数传
即例子:
def wrapper(x):
print(x)
wrapper(111)
wrapper(222)
wrapper(333)
那我们还能不能通过别的方式为函数体代码传参呢??
闭包函数就是这样的一种方式
即:案例
def outer(x):
# x = xxx
def wrapper():
print(x)
return wrapper
f1 = outer(111)
f1() # 111
f2 = outer(222)
f2() # 222
装饰器:
1.什么是装饰器
装饰器就是一个用来为被装饰对象添加新功能的工具
2.为何要用装饰器
开放封闭原则:一旦软件上线运行之后,应该对修改源代码封闭,对扩展功能开放
原则:
1、不修改函数内的源代码
2、不修改函数的调用方式
装饰器就是在遵循原则1和2的前提下,为被装饰对象添加上新功能
3、如何实现装饰器
我们先不说如何实现,我现在有一个需求:为函数index添加统计运行时间的功能
import time
def index():
time.sleep(1) # 模拟功能在做一些io操作
print('from index')
index()
是不是很简单,那我现在又接收到了一个需求:让我们给index函数在不动它函数体代码的情况下,为其添加计时功能?
这时候你可能会问???我为什么要不动函数体代码.是的,一般我们也正是如此,但有时候啊,你一个软件以及上线了,你如果要直接该源代码的话,你可能会因为你一改,改对了还好,该运行的还正常运行,当时当你改错了,你会发现,你有多个地方已经改了,你的程序有可能就直接崩了.而原代码还被你给改了,这就导致了你程序要进入维修状态了.
而装饰器呢就可以解决不改变函数体源代码的情况下,为其添加功能.
居然装饰器这么强大,那我们赶快学习吧.
不要着急.我们在学习的前提,我们看看我们对函数index加上计时功能的几种解决方案
方案一:在index函数的函数体代码内添加功能代码
# 代码示范
import time
def index():
start = time.time()
time.sleep(1)
print('from index')
stop = time.time()
print('run time is %s' %(stop - start))
index()
你会发现,是的你已经添加了计时功能了,但是有一个弊端就是动了函数体代码.
方案二:在调用函数时,再为其加上计时功能
import time
def index():
time.sleep(1)
print('from index')
start = time.time()
index()
stop = time.time()
print('run time is %s' %(stop - start))
你会说这时候我没改函数体代码了吧,但是你这时候又犯了另外一个弊端,你要在每次执行index的时候带上对应的功能代码,重复代码可不是一个好的习惯.
此时你又可以想到,我可以将下面的也放到一个函数内啊,每次执行新的函数不就好啦!
也就是方案三:将index函数放到另外一个函数内,添加计时功能,再调另外一个函数保证添加计时功能
import time
def index():
time.sleep(1)
print('from index')
def wrapper():
start = time.time()
index()
stop = time.time()
print('run time is %s' %(stop - start))
wrapper()
wrapper()
但你有没有发现你的调用方式又变了,你又得去,将每个调用index的改成调用wrapper了.
我们根据上面的方案三,我又有一个需求,将计时添加到多个功能上面
所以就有了方案四:将计时功能添加到多个函数上面
import time
def index():
time.sleep(1)
print('from index')
def wrapper(func):
start = time.time()
func()
stop = time.time()
print('run time is %s' %(stop - start))
wrapper(index)
但是你还是没满足我的不改变调用方式的需求:
这时候你就会想到你学过的闭包了
案例5,将计时功能添加到别的功能上,并且不改变功能调用的形式
提示:案例5最好自己实现.
import time
def index():
time.sleep(1)
print('from index')
# wrapper 是 一个 闭包函数 , 闭的是 wrapper函数 即 timer函数内部定义的一个函数, 包指的是wrapper函数引用了外部函数timer的局部名称,即作用域名字==>func
def timer(func):
"""
使wrapper函数变成即使是一个局部的名称空间,但是在全局也可以调用
:param func: func 即要加上 该功能的 函数
:return: 返回的就是wrapper的内存地址---> 加() 即可以执行内部代码
"""
def wrapper():
"""
作用是为func函数加上计时功能
:return: None
"""
start = time.time()
func()
stop = time.time()
print('run time is %s' % (stop - start))
return wrapper
index = timer(index) # 即将index这个函数当作实参传给了timer, 拿到wrapper函数的内存地址.再将内存地址绑定了一个名称为index
index() # 执行了index 内存地址对应的函数代码体 注意:此时这个index 对应的内存地址是timer返回的内存地址
这时候,你就已经满足了我的所以需求了.不仅加了功能还没改变调用方式
先给你留个悬念:其实在python中index = timer(index)
,还有一个简单的使用方式.明天再讲.