12、Python函数高级(命名空间、作用域、装饰器)
一、名称空间和作用域
1、命名空间(Namespace)
命名空间是从名称到对象的映射,大部分的命名空间都是通过 Python 字典来实现的。
命名空间提供了在项目中避免名字冲突的一种方法。各个命名空间是独立的,没有任何关系的,所以一个命名空间中不能有重名,但不同的命名空间是可以重名而没有任何影响。
1、一般有三种命名空间:
- 内置名称空间(built-in names):存放内置的名字,如
len/eval/enumerate/bytes/max/min/sorted/map/filter....
- 全局名称空间(global names):模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
- 局部名称空间(local names):函数内部的名字都是局部名称空间,不同函数内部的名字互不干涉。
2、命名空间查找顺序:
- 查找顺序:假设我们要使用变量 runoob,则 Python 的查找顺序为:局部的命名空间去 -> 全局命名空间 -> 内置命名空间。
- NameError: name 'runoob' is not defined。
- 执行顺序:先内置(Python解释器启动的时候才会生成)-> 全局(文件执行的时候才会生成)-> 局部(函数调用的时候才会生成)
如果找不到变量 runoob,它将放弃查找并引发一个 NameError 异常:
3、命名空间的生命周期:
命名空间的生命周期取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束。
因此,我们无法从外部命名空间访问内部命名空间的对象。
如下图所示,相同的对象名称可以存在于多个命名空间中。
2、作用域:
作用域就是一个 Python 程序可以直接访问命名空间的正文区域。
全局名称空间和局部名称空间中可能会存在名字相同的变量,但是这两个变量互不影响。
Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。
Python的作用域一共有4种,分别是:
- L(Local):最内层,包含局部变量,比如一个函数/方法内部。
- E(Enclosing):包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为 nonlocal。
- G(Global):当前脚本的最外层,比如当前模块的全局变量。
- B(Built-in): 包含了内建的变量/关键字等。,最后被搜索
对于变量作用域,变量的访问以: L –> E –> G –>B 的 规则查找。
在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内置中找。
举例:
- x = 1
- def func():
- print(x) #
- x = 10
- func()
内置作用域是通过一个名为 builtin 的标准模块来实现的,但是这个变量名自身并没有放入内置作用域内,所以必须导入这个文件才能够使用它。
在Python3.0中,可以使用以下的代码来查看到底预定义了哪些变量:
- import builtins
- print(dir(builtins))
Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问,
如下代码:实例中 msg 变量定义在 if 语句块中,但外部还是可以访问的。如果将 msg 定义在函数中,则它就是局部变量,外部不能访问。
- if True:
- msg = 'I am from Runoob'
- print(msg)
- # 'I am from Runoob'
3、全局变量和局部变量
定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。
局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。
- # 作用域注意点
- x = 1
- def f1(): # 定义阶段x=1
- print(x) #
- def f2():
- x = 2 #此x为f2函数的局部变量,f1无法直接访问
- f1()
- f2()
4、函数对象+作用域应用
- def f1():
- def inner():
- print('from inner')
- return inner
- f = f1() # from inner 。把局部定义的函数inner()放在全局之中
- def bar():
- f()
- bar()
5、global关键字修改全局作用域中的变量
函数内可以访问全局变量,但不能直接更新(修改)其值,可以加上 global 引用以更新变量值 :
- x = 1
- def f1():
- x = 2
- def f2():
- global x # 修改全局
- x = 3
- f2()
- f1()
- print(x) #
6、nonlocal关键字修改嵌套作用域中的变量。
如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要 nonlocal 关键字了
- x = 1
- def f1():
- x = 2
- def f2():
- nonlocal x
- x = 3
- f2()
- print(x) #
- f1()
二、闭包函数
闭包:闭是封闭(函数内部函数),包是包含(该内部函数对外部作用域而非全局作用域的变量的引用)。
闭包指的是:函数内部函数对外部作用域而非全局作用域的引用。
- def outter(x):
- x = 1
- def inner():
- print(x)
- return inner #返回的是函数名(函数对象)
- f = outter(2)
- f() #
- f() #
- f() #
- # 查看闭包的元素
- print(f.__closure__[0].cell_contents) #
闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域。
1、应用领域:
延迟计算(原来我们是传参,现在我们是包起来)、爬虫领域。
- import requests
- def outter(url):
- def get():
- response = requests.get(url)
- print(f"done: {url}")
- return get
- baidu = outter('https://www.baidu.com')
- python = outter('https://www.python.org')
- baidu()
- baidu()
- python()
- python()
三、函数装饰器
装饰器指的是为被装饰器对象添加额外功能。因此定义装饰器就是定义一个函数,只不过该函数的功能是用来为其他函数添加额外的功能。装饰器的实现必须遵循两大原则:
- 不修改被装饰对象的源代码
- 不修改被装饰对象的调用方式
装饰器其实就是在遵循以上两个原则的前提下为被装饰对象添加新功能。
不改变函数体代码,并且不改变函数调用方式,它本质就是一个闭包函数。
- def f1(x):
- def f2():
- print(x) #
- return f2
- f2 = f1()
- f2() # f2()
在不改变当前函数的情况下, 给其增加新的功能:
- def log(pr): # 将被装饰函数传入
- def wrapper():
- print("**********")
- return pr() # 执行被装饰的函数
- return wrapper # 将装饰完之后的函数返回(返回的是函数名)
- @log
- def pr():
- print("我是小小洋")
- pr()
- # **********
- # 我是小小洋
回调函数和返回函数的实例就是装饰器。
四、无参装饰器
举例:
- import time
- def index():
- print('welcome to index')
- time.sleep(1)
- def time_count(func):
- # func = 最原始的index
- def wrapper():
- start = time.time()
- func()
- end = time.time()
- print(f"{func} time is {start - end}") # <function index at 0x102977730> time is -1.0038220882415771
- return wrapper
- index = time_count(index) # index为被装饰函数index的内存地址,即index = wrapper
- index() # wrapper()
1、被装饰函数有返回值:
如果原始的被装饰函数index()有返回值的时候,wrapper()函数的返回值应该和index()的返回值相同,也就是说,我们需要同步原始的index()和wrapper()方法的返回值。
- import time
- def index():
- print('welcome to index')
- time.sleep(1)
- return 123
- def time_count(func):
- # func = 最原始的index
- def wrapper():
- start = time.time()
- res1 = func()
- end = time.time()
- print(f"{func} time is {start - end}") # <function index at 0x102977620> time is -1.0050289630889893
- return res1
- return wrapper
- index = time_count(index)
- res = index()
- print(f"res: {res}") #
- res: 123
2、被装饰函数需要传参:
如果原始的被装饰函数index()方法需要传参,那么我们之前的装饰器是无法实现该功能的,由于有wrapper()=index(),所以给wrapper()方法传参即可。
- import time
- def index():
- print('welcome to index')
- time.sleep(1)
- return 123
- def home(name):
- print(f"welcome {name} to home page")
- time.sleep(1)
- return name
- def time_count(func):
- def wrapper(*args, **kwargs):
- start = time.time()
- res = func(*args, **kwargs)
- end = time.time()
- print(f"{func} time is {start-end}") # <function home at 0x102977378> time is -1.0039079189300537
- return res
- return wrapper
- home = time_count(home)
- res = home('egon')
- print(f"res: {res}") #res: egon
3、装饰器模板
- def deco(func):
- def wrapper(*args,**kwargs):
- res = func(*args,**kwargs)
- return res
- return wrapper
4、装饰器语法糖:
在被装饰函数正上方,并且是单独一行写上@装饰器名
- import time
- def time_count(func): #装饰器
- # func = 最原始的index
- def wrapper(*args, **kwargs):
- start = time.time()
- res = func(*args, **kwargs)
- end = time.time()
- print(f"{func} time is {start-end}") #<function home at 0x102977620> time is -1.0005171298980713
- return res
- return wrapper
- @time_count # home = time_count(home)
- def home(name):
- print(f"welcome {name} to home page") #welcome egon to home page
- time.sleep(1)
- return name
- res = home('egon')
- print(f"res: {res}") #res: egon
五、带参数的装饰器
注意无参装饰器只套两层。
- import time
- current_user = {'username': None}
- def login(func):
- # func = 最原始的index
- def wrapper(*args, **kwargs):
- if current_user['username']:
- res1 = func(*args, **kwargs)
- return res1
- user = input('username: ').strip()
- pwd = input('password: ').strip()
- if user == 'nick' and pwd == '':
- print('login successful')
- current_user['username'] = user
- res1 = func(*args, **kwargs)
- return res1
- else:
- print('user or password error')
- return wrapper
- @login
- def index():
- print('welcome to index')
- time.sleep(1)
- res = index()
#username: nick
#password: 123
#login successful
#welcome to index
我们首先看看三层闭包怎么运用。
- def f1(y):
- def f2():
- x = 1
- def f3():
- print(f"x: {x}") # x: 1
- print(f"y: {y}") # x: 1
- return f3
- return f2
- f2 = f1(2)
- f3 = f2()
- f3()
3、有参三层装饰器:
在函数中嵌入装饰器
- import time
- current_user = {'username': None}
- def auth(engine='file'):
- def login(func):
- def wrapper(*args, **kwargs):
- if current_user['username']:
- res = func(*args, **kwargs)
- return res
- user = input('username: ').strip()
- pwd = input('password: ').strip()
- if engine == 'file':
- print('base of file')
- if user == 'nick' and pwd == '':
- print('login successful')
- current_user['username'] = user
- res = func(*args, **kwargs)
- return res
- else:
- print('user or password error')
- elif engine == 'mysql':
- print('base of mysql, please base of file')
- return wrapper
- return login
- @auth(engine='file')
- def index():
- print('welcome to index')
- time.sleep(1)
- res = index()
username: nick
password: 123
base of file
login successful
welcome to index
六、类装饰器
没错,装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
- class Foo(object):
- def __init__(self, func):
- self._func = func
- def __call__(self):
- print ('class decorator runing')
- self._func()
- print ('class decorator ending')
- @Foo
- def bar():
- print ('bar')
- bar()
- functools.wraps
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子:
- # 装饰器
- def logged(func):
- def with_logging(*args, **kwargs):
- print func.__name__ # 输出 'with_logging'
- print func.__doc__ # 输出 None
- return func(*args, **kwargs)
- return with_logging
- # 函数
- @logged
- def f(x):
- """does some math"""
- return x + x * x
- logged(f)
不难发现,函数 f 被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器里面的 func 函数中,这使得装饰器里面的 func 函数也有和原函数 foo 一样的元信息了。
- from functools import wraps
- def logged(func):
- @wraps(func)
- def with_logging(*args, **kwargs):
- print func.__name__ # 输出 'f'
- print func.__doc__ # 输出 'does some math'
- return func(*args, **kwargs)
- return with_logging
- @logged
- def f(x):
- """does some math"""
- return x + x * x
七、装饰器顺序
一个函数还可以同时定义多个装饰器,比如:
- @a
- @b
- @c
- def f ():
- pass
它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于
- f = a(b(c(f)))
八、装饰器使用场景
现在我们来看一下装饰器在哪些地方特别耀眼,以及使用它可以让一些事情管理起来变得更简单。
授权(Authorization)
装饰器能有助于检查某个人是否被授权去使用一个web应用的端点(endpoint)。它们被大量使用于Flask和Django web框架中。这里是一个例子来使用基于装饰器的授权:
- from functools import wraps
- def requires_auth(f):
- @wraps(f)
- def decorated(*args, **kwargs):
- auth = request.authorization
- if not auth or not check_auth(auth.username, auth.password):
- authenticate()
- return f(*args, **kwargs)
- return decorated
日志(Logging)
日志是装饰器运用的另一个亮点。这是个例子:
- from functools import wraps
- def logit(func):
- @wraps(func)
- def with_logging(*args, **kwargs):
- print(func.__name__ + " was called")
- return func(*args, **kwargs)
- return with_logging
- @logit
- def addition_func(x):
- """Do some math."""
- return x + x
- result = addition_func(4)
- # Output: addition_func was called
12、Python函数高级(命名空间、作用域、装饰器)的更多相关文章
- 十一. Python基础(11)—补充: 作用域 & 装饰器
十一. Python基础(11)-补充: 作用域 & 装饰器 1 ● Python的作用域补遗 在C/C++等语言中, if语句等控制结构(control structure)会产生新的作用域 ...
- Python 函数修饰符(装饰器)的使用
Python 函数修饰符(装饰器)的使用 1. 修饰符的来源修饰符是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等. 修饰符是解决这类问题的绝佳设计, ...
- Python函数07/有参装饰器/多个装饰器装饰一个函数
Python函数07/有参装饰器/多个装饰器装饰一个函数 目录 Python函数07/有参装饰器/多个装饰器装饰一个函数 内容大纲 1.有参装饰器 2.多个装饰器装饰一个函数 3.今日总结 3.今日练 ...
- python 函数名 、闭包 装饰器 day13
1,函数名的使用. 函数名是函数的名字,本质就是变量,特殊的变量.函数名()加括号就是执行此函数. 1,单独打印函数名就是此函数的内存地址. def func1(): print(555) print ...
- 第十七篇 Python函数之闭包与装饰器
一. 装饰器 装饰器:可以拆解来看,器本质就是函数,装饰就是修饰的意思,所以装饰器的功能就是为其他函数添加附加功能. 装饰器的两个原则: 1. 不修改被修饰函数的源代码 2. 不修改被修饰函数的调用方 ...
- python函数对象-命名空间-作用域-02
函数对象 函数是第一对象: # 函数名指向的值可以被当做参数传递 函数对象的特性(*****灵活运用,后面讲装饰器会用到) 函数名可以像变量一样被传递 # 变量可以被传递 name = 'jason' ...
- python函数与模块(装饰器,文件处理,迭代器等)
os模块 os.system('命令') 利用python调用系统命令,命令可以是以列表或者元组内的元素形式* res import os res=os.system('ipconfig') prin ...
- python函数之有参装饰器
一.为什么要有有参装饰器? 来看之前的无参装饰器 # 无参装饰器 def outter(func): def wrapper(*args,**kwargs): start = time.time() ...
- python函数超时,用装饰器解决 func_timeout
https://zhuanlan.zhihu.com/p/39743129 https://www.jianshu.com/p/a7fc98c7af4d https://ixyzero.com/blo ...
随机推荐
- Maven使用tomcat7-maven-plugin
原文地址:https://www.cnblogs.com/mozisss/p/10233366.html 功能: (使用maven中的tomcat插件,就可以将tomcat集成到项目中,效果就是:在不 ...
- QuantLib 金融计算——案例之普通欧式期权分析
目录 QuantLib 金融计算--案例之普通欧式期权分析 概述 普通欧式期权公式法定价 1. 配置期权合约条款 2. 构建期权对象 3. 配置定价引擎 4. 计算 题外话:天数计算规则 Quote ...
- 2、word插入目录、图/表
一.word插入目录 依次对每个标题在“段落”中进行大纲级别选择. 光标定位于目录生成的页面,再“引用”->“目录”->选择“自动目录1/2”,则可自动生成目录.若目录有所更改,则可选择“ ...
- java实现二维码的生成和解读
Java利用QRCode.jar包实现二维码编码与解码 QRcode是日本人94年开发出来的.首先去QRCode的官网http://swetake.com/qrcode/java/qr_java. ...
- 【实战经验】--Xilinx--IPCore--FIFO
2019.12.10补充 结论:先写进的数据在独处时位于高位,后写入的数据在低位,且排序单位为Byte,即先后写入0X01,0X02,读出后也为0x010x02,此外,在写入数据量达到读出数据位宽后5 ...
- MarkDown的常规用法
MarkDown的常规用法 标题 # 一级标题 ## 二级标题 ... ###### 六级标题 列表 第二级 - 和 空格 + 和 空额 * 和 空格 第三级 代码块 多行代码块 3个` 回车 单行代 ...
- <面试题分享> 记两次58面试
说明 来北京找工作,有个猎头看我的简历不错,帮我投了两个58同城的面试,投的都比较高,题也注重原理,较难,这里分享出来,给有需要的人和自己提个醒,保持空杯 面试题内容 2019.05.07 北京58企 ...
- 一起来学习.net core程序使用中介者模式:MediatR插件
中介者模式是一种常见的设计模式,旨再降低程序的耦合性,因为传统的三层模式层层之间需要显示的调用,必须上层依赖下层,耦合性很高,为了解耦,将所有的指令单独放在一个位置处理,其他位置均通过这个位置来间接的 ...
- Winows上简单配置使用kafka(.net使用)
一.kafka环境配置 1.jdk安装 安装文件:http://www.oracle.com/technetwork/java/javase/downloads/index.html 下载JDK安装完 ...
- 设计模式之动态代理(JDK代理)
动态代理跟静态代理一个很重要的区别在于,动态代理是在内存是中的,是在代码编译期后在内存是实现的,而静态代理是我们自己编写代理类,编译后生成class文件.动态代理需要借助两个类:java.lang.r ...