python基础七(函数名称空间及作用域、函数对象、函数嵌套、闭包函数、装饰器)
一 名称空间(namespaces):存放名字的地方,是对栈区的划分。
有了名称空间之后,就可以在栈区中存放相同的名字,详细的名称空间。
分三种
1.1 内建名称空间
存放的名字:存放的python解释器内置的名字
<built-in function print>
存活周期:python解释器启动则产生,python解释器关闭则销毁。
1.2 全局名称空间
伴随python文件的开始执行/执行完毕而产生/回收,是第二个被加载的名称空间,文件执行过程中产生的名字都会存放于该名称空间中
存放的名字:只要不是函数内定义、也不是内置的,剩下的都是全局名称
存活周期:python文件执行则产生,python文件运行完毕后销毁
例: def func(): x=2 y=2 import os
1.3 局部名称空间
存放的名字:在调用函数时,运行函数体代码过程中产生的函数内的名字(伴随函数的调用/结束而临时产生/回收,
函数的形参、函数内定义的名字都会被存放于该名称空间中)
存活周期:在调用函数时存活,函数调用完毕后则销毁
def foo(x): y = 3 # 调用函数时,才会执行函数代码,名字x和y都存放于该函数的局部名称空间中
1.4 名称空间的加载顺序
内置名称空间>全局名称空>局部名称空间
1.5 销毁顺序
加载顺序相反
1.6 查找顺序(处在哪一层,就从哪一层往上找)
查找一个名字,必须从三个名称空间之一找到,查找顺序为:局部名称空间--->全局名称空间--->内置名称空间。
input = 111 def func(): input = 222 print(input) # 局部名称空间 func() print(input) # 全局名称空间
如果局部名称空间和全局名称空间都未找到,就去内置空间找,打印结果为<built-in function input>
# 示范1 x = 111 def func(): print(x) func() # x处于全局名称空间,局部空间找不到,就执行全局名称空间,跟x的位置没有关系
# 111
# 示范2:名称空间的“嵌套”关系是以函数定义阶段为准,与调用位置无关 x = 1 def func(): print(x) def foo(): x = 22 func() foo()
# 1
# 示范3:函数嵌套定义 input = 111 def fun1(): input = 222 def fun2(): input = 333 print(input) fun2() fun1() # 打印结果333
# 范例4: x = 111 def func(): x = 222 print(x) func() print(x)
ps:名称查找,先定义中查找,再一层一层名称空间往上找。
二 作用域--->作用范围
2.1 全局作用域:内置名称空间、全局名称空间
(1)全局存活
(2)全局有效:被所有函数共享
局部作用域:局部名称空间的名字
(1)临时存活
(2)局部有效:函数内有效
2.2 作用域与名字查找的优先级
LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> __builtins__ locals 是函数内的名字空间,包括局部变量和形参 enclosing 外部嵌套函数的名字空间(闭包中常见) globals 全局变量,函数定义所在模块的名字空间 builtins 内置模块的名字空间
三 global与nonlocal
# 范例1: x = 111 def func(): x = 222 print(x) func() print(x) # 222 # 111 # 范例2:如果在局部想要修改全局的名字对应的值(不可变类型),需要用global x = 111 def func(): global x # 申明x这个名字是全局的名字,不要再造新的名字了 x = 222 print(x) func() print(x) # 222 # 222
# 范例3:可变类型不用申明,直接加。 l = [111, 222] def func(): l.append(333) func() print(l)
# [111, 222, 333]
# nonlocal(了解):修改函数外层函数包含的名字对应的值(不可变类型) def f1(): x = 2 def f2(): nonlocal x x = 3 f2() # 调用f2(),修改f1作用域中名字x的值 print(x) # 在f1作用域查看x f1() # 3
四 名称空间名字查找复习及总结
# 例1 x = 1 def func(): print(x) def foo(): x = 22 func() x = 333 foo() # 333 # 例2 input = 111 def f1(): def f2(): input=333 print(input) input=222 print('f1 out--->', input) f2() f1() # f1 out---> 222 # 333 名称空间namespaces 名字---》栈区 名称空间是对栈的一种划分,真正存在的是栈区,名称空间只是一种虚拟的划分 内置名称空间 全局名称空间 局部名称空间 重点1: 名词查找:当前所在的位置向外查找 局部---》全局---》内置 重点2: 名称空间只有优先级之分,本身并无嵌套关系,画图只是为了理解 重点3: 名称空间的嵌套关系决定了名字的查找顺序 而名称空间的嵌套关系是以函数定义阶段为准的, 既函数的嵌套关系与名字的查找顺序是在定义阶段就已经确定好的 全局作用域:内置名称空间+全局名称空间 全局存活,全局有效 局部作用域: 临时存活,局部有效
五 函数对象
# 精髓:可以把函数当成变量去用 # func=内存地址 def func(): print('hello world') # 1、可以赋值 # f = func() print(func()) # 是return的返回值 # print(f, func) # f是return的返回值None,func就是内存地址 # 2、可以当做函数的参数传给另一个函数 def foo(x): print(x) foo(func) # foo(func的内存地址) # <function func at 0x0000021B8BBD9280> # 3、可以当做函数另外一个函数的返回值 def foo(x): return x res = foo(func) print(res) res() # <function func at 0x0000011EFFDD9280> # hello world # 4、可以当做容器类型的一个元素 l = [func, ] print(l) l[0]() # [<function func at 0x000001B666C49280>] # hello world
# 例:ATM取款功能 def quit(): print('退出') def enter(): print('登录') def transfer(): print('转账') def withdrwal(): print('取款') def balance_inquriy(): print('查询余额') choice_dic = {'1': enter, '2': transfer, '3': withdrwal, '4': balance_inquriy} while True: print(''' 0 退出 1 登录 2 转账 3 取款 4 查询余额''') choice = input('请选择:').strip() if not choice.isdigit(): print('必须输入数字,ox:') continue if choice == '0': break if choice in choice_dic: choice_dic[choice]() else: print('请选择正确的选项')
六 函数嵌套
# 函数嵌套 # 1、函数的嵌套调用:在调用一个函数的过程中又调用其他函数 # 求最大数: def max(x, y): if x > y: return x else: return y def max1(a, b, c, d): res1 = max(a, b) res2 = max(res1, c) res3 = max(res2, d) return res3 res = max1(1, 2, 5, 4) print(res) # 2、函数的嵌套定义:在函数内定义其他函数 def f1(): def f2(): pass
六 闭包函数
1、大前提:
闭包函数=名称空间与作用域+函数嵌套+函数对象
核心点:名字的查找关系是以函数定义阶段为准
2、什么是闭包函数
“闭”函数指的该函数内嵌函数
“包”函数指的该函数包含对外层函数作用域名字的引用(不是全局作用域)
# 闭包函数:名称空间与作用域的应用+函数嵌套 def f1(): x=111 def f2(): print(x) f2() x=222 def foo(): x=333 f1() foo() # 111 # 闭包函数:函数对象,函数作为return的返回值 def f1(): x = 111 def f2(): print(x) return f2 # 返回f2的内存地址 f = f1() # f2的内存地址,<function f1.<locals>.f2 at 0x000001BFAC6071F0> print(f) f() # f2函数的调用,把f2内嵌函数当成了全局作用域使用 # <function f1.<locals>.f2 at 0x000001A390AB24C0> # 111
3、为何要有闭包函数--->闭包函数的应用
两种为函数体传参的方式
# 方式一:直接把函数体需要的参数定义成形参 def f(x): print(x) f(1)
# 方式二:函数对象方式进行传参 def f1(): x = 1 def f2(): print(x) return f2 f = f1() f() # 1 # 改进 def f1(x): # x=1 def f2(): print(x) return f2 f = f1(1) f() # 改进2 def f1(x): # x=1 def f2(y, m): print(f2) # <function f1.<locals>.f2 at 0x000001BBEAD571F0> print(x) print(y, m) return f2 f = f1(1) f(2, 3) print(f) # <function f1.<locals>.f2 at 0x000001BBEAD571F0>,f和f2的内存地址是一样的,f的调用,就是f2的调用
七 装饰器
1、知识储备
*args,**kwargs
名称空间与作用域
函数对象
函数的嵌套定义
闭包函数
2、装饰器
1)、什么是装饰器
“器”指的是工具,可以定义成函数
“装饰”指的是为其他事物添加额外的东西点缀
装饰器:指的是定义一个函数,该函数是用来为其他函数添加额外的功能
2)、为何要用装饰器
开放封闭原则
开放:指的是对拓展功能是开放的
封闭:指的是对修改源代码是封闭的
装饰器就是在不修改装饰器对象源代码以及调用方式的前提下为被装饰对象添加新功能
3、如何用装饰器
需求:在不修改index函数的源代码以及调用方式的前提下为其添加统计运行时间的功能
# 方案一:在原代码基础上修改 import time def index(x, y): start = time.time() time.sleep(1) print('hello world:{},{}.'.format(x, y)) stop = time.time() index('xiaobao', 'lq') # 方案二:没有修改被装饰对象的调用方式,也没有修改其源代码,并且加上了新功能,但是代码冗余 import time def index(x, y): time.sleep(1) print('hello world:{},{}.'.format(x, y)) start = time.time() index('xiaobao', 'lq') stop = time.time() print(stop - start) # 方案三:解决了方案二代码冗余问题,但带来一个新问题既函数的调用方式改变 import time def index(x,y): time.sleep(1) print('hello world:{},{}.'.format(x,y)) def wrapper(): start = time.time() index('xiaobao', 'lq') stop = time.time() print(stop - start) wrapper()
# 方案三优化一:如何在方案三的基础上不改变函数的调用方式(方案三的优化一,将index的参数写活了) import time def index(x, y): time.sleep(1) print('hello world:{},{}.'.format(x, y)) def wrapper(*args, **kwargs): start = time.time() index(*args, **kwargs) stop = time.time() print(stop - start) wrapper('xiabao', 'lq') # 方案三优化二:在优化一的基础上把被装饰对象写活了,原来只能装饰index import time def index(x, y): time.sleep(1) print('hello world:{},{}.'.format(x, y)) print(index) def outer(func): # func=index(闭包函数) def wrapper(*args, **kwargs): start = time.time() func(*args, **kwargs) # func就是闭包函数中例子中的x的查找 stop = time.time() print(stop - start) return wrapper index = outer(index) # 这里的index实际就是wrapper的内存地址 index('xiaobao', 'lq') print(index) # 方案三优化(被装饰函数有return的情况):最终优化完成。 import time def home(m, n): time.sleep(1) print('welcome to home:{}和{}'.format(m, n)) return 'thanks' res1 = home('xiaobao', 'zd') print('未被装饰的返回值--->', res1) def outer(func): def wrapper(*args, **kwargs): start = time.time() func(*args, **kwargs) stop = time.time() print(stop - start) return wrapper home = outer(home) res2 = home('xiaobao', 'zd') print('装饰后的返回值--->', res2) # welcome to home:xiaobao和zd # 未被装饰的返回值---> thanks # welcome to home:xiaobao和zd # 1.0016324520111084 # 装饰后的返回值---> None
# 装饰器 def outer(func): def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print(stop - start) return res return wrapper # 在被装饰器对象正上方的单独一行写@装饰器名字 # index函数 @outer # index=outer(index) def index(x, y): time.sleep(1) print('hello world:{},{}.'.format(x, y)) # home函数 @outer # home=outer(home) def home(m, n): time.sleep(1) print('welcome to home:{}和{}'.format(m, n)) return 'thanks' index('xiaobo', 'lq') res3 = home('xiaobao', 'zd') print(res3)
# hello world:xiaobo,lq.
# 1.0000083446502686
# welcome to home:xiaobao和zd
# 1.0008976459503174
# thanks
# 延伸:多个装饰器,加载顺序与运行顺序 @deco1 # index=deco1(deco2.wrapper的内存地址) @deco2 # deco2.wrpper的内存地址=deco2(deco3.wrpper的内存地址) @deco3 # deco3.wrpper的内存地址=deco3(index) def index(): pass
# 总结无参装饰器模板
def outer(func): def wrapper(*args, **kwargs): # 1、调用原函数 # 2、为其增加新功能 res = func(*args, **kwargs) return res return wrapper
# 实现注册的装饰功能
def auther(func): def wrapper(*args, **kwargs): inp_name = input('请输入你的名字: ').strip() inp_pwd = input('请输入你的密码: ').strip() with open(r'user.txt', mode='r', encoding='utf-8') as f: for line in f: # 生成的line是字符串(含了.read的功能),先.strip去除字符串前后的\n,再.split以':'为界切分成列表。 # print(line,end='') #liuqiao:123\n print(line.strip('\n').split(':')) u, p = line.strip('\n').split(':') # 把用户输入的名字与密码与读出内容做比对 if inp_name == u and inp_pwd == p: print('登录成功') res = func(*args, **kwargs) print(u, p) return res else: print('账号名或者密码错误') return wrapper @auther def index(): print('开始程序接下来的工作---》') index() # 请输入你的名字: xiaobao # 请输入你的密码: 123 # 登录成功 # 开始程序接下来的工作---》 # xiaobao 123
八 装饰器补充(wrapper被装饰)
# 偷梁换柱,既将原函数名指向的内存地址偷梁换柱成wrapper函数,所以应该将wrapper做的跟原函数一样才行 def outter(func): def wrapper(*args, **kwargs): res = func(*args, **kwargs) return res wrapper.__name__ = func.__name__ # 手动添加将原函数的属性赋值给wrapper函数,这里wrapper成了被装饰对象。 wrapper.__doc__ = func.__doc__ return wrapper @outter # index=outter(index) def index(x, y): ''''这个是主页功能''' print(x, y) index(1, 2) # wrapper(1,2) print(index.__name__) print(index.__doc__) # 优化二 from functools import wraps def outter(func): @wraps(func) # 自动将原函数的属性赋值给wrapper函数,这里wrapper成了被装饰对象。 def wrapper(*args, **kwargs): res = func(*args, **kwargs) return res # wrapper.__name__= func.__name__ # wrapper.__doc__= func.__doc__ return wrapper @outter # index=outter(index) def index(x, y): ''''这个是主页功能''' print(x, y) index(1, 2) # wrapper(1,2) print(index.__name__) print(index.__doc__) print(index.__code__)
九 有参装饰器
1、知识储备:
from functools import wraps def outter(func): @wraps(func) def wrappper(*args, **kwargs): print('好累') res = func(*args, **kwargs) return res return wrappper # 由于语法糖@的限制,outter函数只能有一个参数,并且该函数只用来接受被装饰对象的内存地址 @outter # index=outter(index) def index(x, y): print(x, y) index(1, 2)
偷梁换柱之后
index的参数什么样子,wrapper的参数就应该什么样子
index的返回值是什么样子,wrapper的返回值就应该什么样子
index的属性什么样子,wrapper的属性就应该什么样子(@wraps的使用)
2、有参装饰器应用
注册功能的装饰器,用户名和密码来之三种存储(file,sql,ldp)
from functools import wraps def auther(db): def deco(func): @wraps(func) def wrapper(*args, **kwargs): user = input('输入名字:').strip() pwd = input('请输入密码:').strip() if user == "xiaobao" and pwd == "123": if db == "file": print('账户来之文件') res = func(*args, **kwargs) return res elif db == "sql": print('账户来之数据库') res = func(*args, **kwargs) return res elif db == "lap": print('账户来之lap') res = func(*args, **kwargs) return res else: print("不支持该db类型") else: print("账号或密码错误") return wrapper return deco @auther(db='file') # myfile=deco(myfile)-->@deco--->deco=auth(db='file')--->@auth(db='file') def myfile(x, y): print(x, y) @auther(db='sql') def mysql(x, y): print(x, y) @auther(db='lap') def mylap(x, y): print(x, y) # myfile(1, 2) mysql(3, 4) # 输入名字:xiaobao # 请输入密码:123 # 账户来之数据库 # 3 4
# 总结:有参装饰器模板
def auth(x, y, z): def deco(func): def wrapper(*args, **kwargs): # 这些地方就是要加的x,y,z参数的地方(也是加功能) print(x+y+z) res = func(*args, **kwargs) # 这些地方就是要加的x,y,z参数的地方(也是加功能) return res return wrapper return deco @auth(x=1, y=2, z=3) def index(): pass index() # 6
十 叠加多个装饰器
叠加多个装饰器的加载、运行分析 # index=deco3(deco2(deco1(index))) # @deco1 # index=deco1(deco2.wrapper的内存地址) # @deco2 # deco2.wrpper的内存地址=deco2(deco3.wrpper的内存地址) # @deco3 # deco3.wrpper的内存地址=deco3(index) # def index(): # pass def deco1(func1): def wrapper1(*args,**kwargs): # func1就是wrapper2的内存地址 print('deco1.wrapper1运行') res1=func1(*args,**kwargs) print('加载顺序deco1') return res1 return wrapper1 def deco2(func2): def wrapper2(*args,**kwargs): # func2就是wrapper3的内存地址 print('deco2.wrapper2运行') res2=func2(*args,**kwargs) print('加载顺序deco2') return res2 return wrapper2 def deco3(x): def outter(func3): def wrapper3(*args,**kwargs): # func3就是被装饰对象index函数的内存地址 print('deco3.wrapper3运行',x) res3=func3(*args,**kwargs) print('加载顺序deco3') return res3 return wrapper3 return outter # 加载顺序自下而上 @deco1 # -->index=wrapper1(wrapper2的内存地址)------------>index=wrapper1的内存地址 @deco2 # -->index=wrapper2(wrapper3的内存地址)------------>index=wrapper2的内存地址 @deco3('xiaobao') # -->@outter3-->index=outter3(index)---->index=wrapper3的内存地址 def index(x,y): print(x,y) # print(index) # <function deco1.<locals>.wrapper1 at 0x00000141D60F7790> # 执行顺序自上而下,既wrapper1-->wrapper2-->wrapper3-->index函数体-->return res3-->return res2-->return1 res1 # index('zd','lq') ''' 函数调用结果 deco1.wrapper1运行 deco1.wrapper2运行 deco3.wrapper3运行 xiaobao zd lq 加载顺序deco3 加载顺序deco2 加载顺序deco1 '''