python3【基础】-装饰器
要理解充分理解python的装饰器,有充分理解下述三个知识点为前提:
- python作用域规则
- 函数即对象
- 闭包
一、python作用域规则:
首先介绍python中的作用域规则。python的作用域规则遵循LEGB规则,这四个字母是什么意思呢?下面将逐一介绍:
- L:local函数内部作用域
- E:enclosing函数内部与内嵌函数之间
- G:global全局作用域
- B:build-in内置作用域
上述四个作用域的优先级表现为L>E>G>B,具体在代码中是什么意思呢?我们举例来说明。
1. local内部作用域
有如下代码,在foo()函数内部和外部都有一个num变量,在函数内部调用num变量,python优先调用了内部的num。函数内部的num的仅仅在函数内部有效,即其生存周期为函数调用结束后内部的num变量失效。
num = 90 def foo():
num = 100
print("函数内部变量num的地址是:{_id}".format(_id=id(num)))
print("内部num:{_num}".format(_num=num)) foo()
print("函数外部变量num的地址是:{_id}".format(_id=id(num)))
print("外部num:{_num}".format(_num=num)) # 执行上述代码获得如下结果:
# 函数内部变量num的地址是:4305316064
# 内部num:100
# 函数外部变量num的地址是:4305315744
# 外部num:90
2. enclosing函数内部与内嵌函数之间
python的函数支持嵌套定义,即在一个函数内部再定义函数,将函数看做一个对象并进行返回。这样,在调用外部函数的时候可以使用一个变量将内部的函数接收。
我们在foo1内部定义了foo2函数,并将foo2返回。所谓的enclosing是指foo2和foo1之间的作用域。
代码段1:
def foo1():
num = 100 def foo2():
num = 90
print("foo2-num_id:{_id}".format(_id=id(num)))
print("foo2-num:{_num}".format(_num=num)) print("foo1-num_id:{_id}".format(_id=id(num)))
print("foo1-num:{_num}".format(_num=num)) return foo2
foo2 = foo1()
foo2() # 执行上述代码得:
# foo1-num_id:4305316064
# foo1-num:100
# foo2-num_id:4305315744
# foo2-num:90
代码段2:
def foo1():
num = 100 def foo2():
# num = 90
print("foo2-num_id:{_id}".format(_id=id(num)))
print("foo2-num:{_num}".format(_num=num)) print("foo1-num_id:{_id}".format(_id=id(num)))
print("foo1-num:{_num}".format(_num=num)) return foo2 foo2 = foo1()
foo2() # 执行上述代码得:
# foo1-num_id:4305316064
# foo1-num:100
# foo2-num_id:4305316064
# foo2-num:100
对比代码段1和代码段2可以看出,在foo2内部,调用num变量时,会优先调用内部的num变量,假如内部变量num不存在,则去外层寻找num。
3. global全局作用域
global作用域是指整个python文件。 1 num = 1 2 3 def foo1():
num = 2
def foo2():
num = 3
print(num)
print(num)
return foo2
print(num)
foo2 = foo1()
foo2()
# 执行上述代码得:
1
2
3
上述代码分别定了三个num变量,其中第1行中的num属于全部作用域范围。
4. build-in内置作用域
def Max(a,b):
return max(a, b) print(Max(1,2))
上述代码中,编译器在编译Max函数的时候,发现Max函数包含max函数,而局部作用域和全局作用域都没有max函数,编译器就会去内置作用域中查找max。
二、函数即对象:
在python这个世界里,函数和我们之前的[1,2,3],'abc',8等一样都是对象,而且函数是最高级的对象(对象是类的实例化,可以调用相应的方法,函数时包含变量的对象的对象)。

通过上图,咱们来理解一下python变量在内存中的情况。
我们定义一个a=8,其实a存储的并不是‘8’这个值,而是‘8’在内存中的地址。将进行b=a这个操作时,是将a中包含‘8’的内存地址赋值给b,这样的话b也就指向8了。此时,a和b在值上表现出一样。同样的道理,我们定义一个函数foo(),它执行print('ok') 这个操作,此时,编译器在编译foo()这个函数的时候,会将foo这个函数名指向print('ok')这条语句在内存中的地址。在python中,函数是一个对象,既然是对象,我们就可以对其进行更改(赋值),执行bar=foo这个操作的意思就是bar这个变量指向print('ok')在内存中的地址。
根据上述理解,我们可以得出下述两个结论:
1. 函数名可以赋给其它变量
def foo():
print('i am foo...') bar = foo
bar() # 执行上述代码,打印'i am foo...'
2. 函数名可以作为函数的参数进行传递,类型字符串、列表等
def foo():
print('i am foo...') def func(f):
f() func(foo) # 执行上述代码,打印'i am foo...'
3. 函数名可以作为返回值返回
def foo1():
def foo2():
print('i am foo2')
return foo2 foo = foo1()
foo() # 执行上述代码,打印'i am foo2...'
三、闭包
首先来看一个闭包的实例。
def outer(num):
def inner():
print(num)
return inner
func = outer(10)
func()
上述代码便是一个闭包的实例。我们来分析一下代码:
1. 参数(变量)num的作用域为整个outer函数,其中在inner函数中也是有效的。
2. inner函数(根据前面的内容,我们可以将其理解为一个变量),其作用域也在函数outer内部有效。
但是,我们在代码中调用outer函数,此时它返回inner函数被func接收。我们再次执行func函数,它成功地打印了num的值,此时,便有一个疑问,num变量已经跑到它的作用域外面去了,为何它还是有效的?
原来,上述代码构成了一个闭包函数。
所谓的闭包函数,由两部分构成。一个是内部函数,另一个是定义内部函数时的环境,其中num这个值便是当时定义inner函数时的环境之一。
闭包 = 函数块+定义函数时的环境
return表面上虽然只返回了innner函数,但实际上它将当时定义inner函数时所处的环境一起返回(当然,这个环境并不是单纯的只有num变量,还有其它很多....)。
闭包的完整定义:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)
四、装饰器
说了这么多,终于可以说到装饰器了。
装饰器本质上是一个函数,该函数用来处理其它函数,它可以让其它函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也是函数对象。它经常用于切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为了已经存在的对象添加额外的功能。
我们来看一个装饰器的实例:
import time def show_time(func):
def inner():
start = time.time()
func()
end = time.time()
print("spend %s" % (end - start))
return inner @show_time # foo = show_time(foo)
def foo():
print('foo...')
time.sleep(2) @show_time # bar = show_time(bar)
def bar():
print('bar...')
time.sleep(2)
此时有两个函数foo()和bar(),我们想查看一下这两个函数的执行时间。此时我们定义一个show_time函数,该函数的参数是一个函数名字,返回值是一个函数。在函数内部,执行传入的函数并且打印其执行的时间。
函数show_time就是装饰器,它把真正的业务方法func包裹在函数里面,看起来像foo被上下时间函数装饰了。在这个例子中,函数进入和退出时,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程。
@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作。
五、带参数的装饰器
1. 不定长参数的装饰器
#***********************************不定长参数
import time def show_time(func): def wrapper(*args,**kwargs):
start_time=time.time()
func(*args,**kwargs)
end_time=time.time()
print('spend %s'%(end_time-start_time)) return wrapper @show_time #add=show_time(add)
def add(*args,**kwargs): time.sleep(1)
sum=0
for i in args:
sum+=i
print(sum) add(2,4,8,9)
2. 装饰器本身含有参数
import time
def time_logger(flag=0):
def show_time(func):
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
print('spend %s' % (start - end))
if flag:
print('打印成功')
return wrapper
return show_time
@time_logger(3)
def add(*args, **kwargs):
time.sleep(1)
sum = 0
for i in args:
sum += i
print(sum)
add(1, 2, 3, 4, 5)
@time_logger(3)做了两件事情:
1. time_logger(3) 得到闭包函数show_time,里面保存环境变量flag
2. @show_time add=show_time(add)
上面的time_logger是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器(一个含有参数的闭包函数)。当我们调用@time_logger(3)时,python能够发现这一层的封装,并把参数传递到装饰器。
python3【基础】-装饰器的更多相关文章
- python基础——装饰器
python基础——装饰器 由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数. >>> def now(): ... print('2015-3-25 ...
- python基础—装饰器
python基础-装饰器 定义:一个函数,可以接受一个函数作为参数,对该函数进行一些包装,不改变函数的本身. def foo(): return 123 a=foo(); b=foo; print(a ...
- (转)Python3.5——装饰器及应用详解
原文:https://blog.csdn.net/loveliuzz/article/details/77853346 Python3.5——装饰器及应用详解(下)----https://blog.c ...
- day5学python 基础+装饰器内容
基础+装饰器内容 递归特性# 1.必须有一个明确的结束条件# 2.每次进入更深一层递归时,问题规模相比上次递归应有所减少# 3.递归效率不高 def run(n): print(n) if int(n ...
- python3.7 装饰器
#!/usr/bin/env python __author__ = "lrtao2010" #python3.7 装饰器 #装饰器 ''' 定义:本质就是一个函数,作用是为其他函 ...
- python3练习-装饰器
在廖雪峰的官方网站学习装饰器章节时,初步理解类似与面向切面编程.记录一下自己的课后习题解法. 问题: 请编写一个decorator,能在函数调用的前后打印出'begin call'和'end call ...
- python基础-装饰器,生成器和迭代器
学习内容 1.装饰器 2.生成器 3.迭代器 4.软件目录结构规范 一:装饰器(decorator) 1.装饰器定义:本质就是函数,用来装饰其他函数,即为其他函数添加附加功能. 2.装饰器原则:1)不 ...
- python基础-装饰器
一.什么是装饰器 装饰器本质就是函数,功能是为其他函数附加功能 二.装饰器遵循的原则 1.不修改被修饰函数的源代码 2.不修改被修饰函数的调用方式 三.实现装饰器的知识储备 装饰器=高阶函数+函数嵌套 ...
- Python自动化 【第四篇】:Python基础-装饰器 生成器 迭代器 Json & pickle
目录: 装饰器 生成器 迭代器 Json & pickle 数据序列化 软件目录结构规范 1. Python装饰器 装饰器:本质是函数,(功能是装饰其它函数)就是为其他函数添加附加功能 原则: ...
- python 基础——装饰器
python 的装饰器,其实用到了以下几个语言特点: 1. 一切皆对象 2. 函数可以嵌套定义 3. 闭包,可以延长变量作用域 4. *args 和 **kwargs 可变参数 第1点,一切皆对象,包 ...
随机推荐
- python - 流程控制基础习题
#打印1 到100 的和** i = 0 s = 0 while i <100: i += 1 s += i print(s) print('1+2+,,100=',s) #打印字符A~Z fo ...
- File、Paths和Files类的使用详解
Paths:通过get()方法返回一个Path对象,Path用于表示文件路径和文件. Files:提供了大量处理文件的方法,例如文件复制.读取.写入,获取文件属性.快捷遍历文件目录等..... Fil ...
- cuda cudnn anaconda gcc tensorflow 安装及环境配置
1.首先,默认你已经装了适合你的显卡的nvidia驱动. 到 http://www.nvidia.com/Download/index.aspx 搜索你的显卡需要的驱动型号 那么接下来就是cuda的 ...
- Linux(CentOS7)设置自动备份数据库到阿里云OSS
环境:阿里云服务器CentOS7.4 + MySQL5.6 基本思路: 1.编写shell脚本,备份数据库到指定目录下 2.编写Python脚本,把文件上传到OSS 3.把shell脚本和Python ...
- JQuery模拟网页中自定义鼠标右键菜单
题外话.......最近在开发一个网站项目的时候,需要用到网页自定义右键菜单,在网上看了各路前辈大神的操作,头晕目眩,为了达到目的,突然灵机一动,于是便有了这篇文章. 先放个效果图(沾沾自喜,大神勿喷 ...
- 服务端接收不到ajax post请求的参数
问题描述 服务端使用request.getParameter()接收不到post请求的参数,导致业务逻辑抛出空指针异常. 解决途径 tomcat对post请求支持的字节数不受限制的配置发生变化.在to ...
- 树莓派3B+学习笔记:7、挂载exfat格式U盘
树莓派的官方系统,默认不支持exfat格式U盘挂载. 插入exfat格式U盘会出现以下错误提示: 安装exfat-fuse后可以正常识别,需要在命令行执行以下命令,按“y”键回车确认: sudo ap ...
- MySQL数据库删除数据(有外键约束)
在MySQL中删除一张表或一条数据的时候,出现有外键约束的问题,于是就去查了下方法: SELECT @@FOREIGN_KEY_CHECKS; 查询当前外键约束是否打开 ; 设置为1的时候外键约束是打 ...
- MongoDB分片介绍
本文简单介绍MongoDB的分片功能,对分片进行了概述,具体的功能详解,后续文章会陆续推出 分片是把数据分配到多个服务器上的一种方式,MongoDB使用分片实现大数据部署以及高吞吐操作. 大数据以及高 ...
- ASP.NET Core 资源打包与压缩
ASP.NET Core 资源打包与压缩 在ASP.NET 中可以使用打包与压缩来提高Web应用程序页面加载的性能. 打包是将多个文件(CSS,JS等资源文件)合并或打包到单个文件.文件合并可减少We ...