Python装饰器详细介绍


Posted in Python onMarch 25, 2022

装饰器

一、介绍

  • :代表函数的意思。装饰器本质就是是函数
  • 功能:装饰其他函数,就是为其他函数添加附加功能 
  • 被装饰函数感受不到装饰器的存在
  • 原则: 

    不能修改被装饰的函数的源代码(比如线上环境)

    不能修改被装饰的函数的调用方式 

  • 实现装饰器知识储备: 

    函数即是“变量”

    高阶函数

    嵌套函数

高阶函数+嵌套函数=>装饰器

二、通过高阶函数+嵌套函数==>实现装饰器

先分析以下两段代码能不能运行?

def foo():
    print("in the foo")
    bar()
def bar():
    print("in the bar")

foo()
def foo():
    print("in the foo")
    bar()
foo()
def bar():
    print("in the bar")

第二段代码报错:

NameError: name 'bar' is not defined

1、变量知识回顾

定义变量: 
如:定义变量:x=1,会在内存中找块内存空间把“1”存进去,把“1”的内存地址给x  

前面提到:函数即变量

# 定义函数
def test():
    pass
# 就相当于把函数体赋值给test变量
test = '函数体'  # 函数体就是一堆字符串而已
# 只不过函数调用要加上小括号调用
test()

python内存回收机制,是解释器做的。解释器到底怎么去回收这个变量? 
python解释器当中有种概念叫做引用计数。什么叫引用计数呢? 
比如:定义x=1,之后又定义了y=1或y=x,实际上又把内存空间“1”的内存地址赋值给y 
这里x代表一次引用,y代表一次引用。加起来两次引用。 
python什么时候会把“1”这个内存空间清空呢?会回收内存呢? 
当x这个变量没有了,y这个变量也没有了,便会把“1”这个内存空间清掉

del x  # 删的只是变量名,内存中的值是解释器回收

匿名函数

lambda x:x*x

匿名函数没有函数名,没有引用,所以会被垃圾回收机制立马回收掉。 
所以匿名函数要赋值给变量,把函数体赋值给变量名

calc = lambda x:x*x
print(calc(4))

现在可以再理解下最开始两段代码能不能运行的原因。 

2、高阶函数(装饰器前奏)

什么叫高阶函数呢:

  • 把一个函数名当做形实传给另外一个函数
  • 返回值中包含函数名
def f1():
    print("in the func1")
def test1(func):
    print(func)
test1(f1)

运行结果(打印内存地址)

<function func1 at 0x000002805DE12378>

如下代码,能不能运行:

def f1():
    print("in the func1")
def test1(func):
    print(func)
    func()
test1(f1)

函数即变量,像“x=1,y=x”,同样f是一个是一个函数,可不可以像一个变量一样来回赋值呢?

import time
def func1():
    print("in the func1")
    time.sleep(1)
def test1(func):
    start_time = time.time()
    func()
    stop_time = time.time()
    print("the func run time is %s" %(stop_time-start_time))
test1(func1)

到这里,貌似实现了装饰函数的功能。 
看上面装饰器的原则: 
这里:没有修改func1的源代码,但是调用方式改变了。现在是test1(func1),之前是func1() 
现在能做到哪一点呢? 
把一个函数名当做实参传给另外一个函数(不修改被装饰的函数源代码的情况下为其添加功能)

2) 下面用第二个条件(返回值中包含函数名),做另外一个高阶函数

import time
def func2():
    time.sleep(1)
    print("in the func2")
def test2(func):
    print(func)
    return(func)
print(test2(func2))

运行结果:

<function func2 at 0x00000162F3672378>
<function func2 at 0x00000162F3672378>

把函数内存地址都打印出来了,看到这么多内存地址,有什么想法? 
加上小括号就能运行。 
上面代码“test2(func2())”和“test2(func2)”有什么区别?加上小括号是函数返回结果,不加是函数内存地址。所以加上小括号就不符合高阶函数定义了。 
既然以后有了函数的内存地址,是不是可以赋值给其他变量?下面

import time
def func2():
    print("in the func2")
    time.sleep(1)
def test2(func):
    print(func)
    return(func)
t = test2(func2)
print(t)
t()

好像还没什么用,怎么让他有用呢? 
把test2(func2)赋值给func2

import time
def func2():
    print("in the func2")
    time.sleep(1)
def test2(func):
    print(func)
    return(func)
func2 = (test2(func2))
func2()

这就是高阶函数的第二个好处:返回值中包含函数名(不修改函数的调用方式)

3、嵌套函数(装饰器前戏)

嵌套函数:在一个函数体内,用def去声明一个函数

def foo():
    print("in the foo")
    def bar():
        print("in the bar")
    bar()
foo()

看一下下面的代码是不是嵌套:

def foo():
    print("in the foo")
def bar():
    foo()
bar()

注意函数嵌套和函数调用区别  

局部作用域和全局作用域的访问顺序:

x = 0
def grandpa():
    # x = 1
    def dad():
        x = 2
        def son():
            x = 3
            print(x)
        son()
    dad()
grandpa()

三、装饰器

1、装饰器

前面铺垫了那么多,现在开讲正题:装饰器 
先用高阶函数实现给函数不修改源代码的情况下添加功能

import time
def deco(func):
    start_time = time.time()
    func()
    stop_time = time.time()
    print("the func tun time is %s" %(stop_time-start_time))
def test1():
    time.sleep(1)
    print("in the test1")
def test2():
    time.sleep(1)
    print("in the test2")
deco(test1)
deco(test2)

按照上面说的,如何实现不改变调用方式?直接“test1 = deco(test1)”和“test2 = deco(test2)”吗? 
别忘记了,第二种方式,高阶函数要加上return,如下

import time
def deco(func):
    start_time = time.time()
    return func()
    stop_time = time.time()
    print("the func tun time is %s" %(stop_time-start_time))
def test1():
    time.sleep(1)
    print("in the test1")
def test2():
    time.sleep(1)
    print("in the test2")
test1 = deco(test1)
test2 = deco(test2)
deco(test1)
deco(test2)

虽然没有修改源代码和调用方式,但是函数加上return,函数就结束了,然并卵。怎么实现呢? 
前面一直在用高阶函数,还没有用嵌套函数,加上嵌套函数能不能实现呢?看一下

import time
def timer(func):  # timer(test1)  func=test1
    def deco():
        start_time = time.time()
        func()
        stop_time = time.time()
        print("the func tun time is %s" %(stop_time-start_time))
    return deco  # 返回deco的内存地址
def test1():
    time.sleep(1)
    print("in the test1")
def test2():
    time.sleep(1)
    print("in the test2")
print(timer(test1))  # 可见:返回deco的内存地址
test1 = timer(test1)
test1()
timer(test2)()

到此,完成实现了装饰器的功能。但是还是有点麻烦,如何能不要“test1 = timer(test1)”, 
python解释器提供了语法糖“@”符合,给哪个函数新增功能,就加在哪个函数头部

import time
def timer(func):  # timer(test1)  func=test1
    def deco():
        start_time = time.time()
        func()
        stop_time = time.time()
        print("the func tun time is %s" %(stop_time-start_time))
    return deco  # 返回deco的内存地址
@timer
def test1():
    time.sleep(1)
    print("in the test1")
@timer
def test2():
    time.sleep(1)
    print("in the test2")

test1()
test2()

2、有参装饰器

前面实现了装饰器的功能,但是如果函数有参数,能不能也能运行呢

import time
def timer(func):  # timer(test1)  func=test1
    def deco():
        start_time = time.time()
        func()
        stop_time = time.time()
        print("the func tun time is %s" %(stop_time-start_time))
    return deco  # 返回deco的内存地址
@timer
def test1():
    time.sleep(1)
    print("in the test1")
@timer
def test2(name):
    time.sleep(1)
    print("in the test2",name)

test1()
test2()

报错:丢失参数

TypeError: test2() missing 1 required positional argument: 'name'

@timer 相当于 test2=timer(test2) =deco 
test2() 相当于运行deco(),所以没指定参数,报错。 
如何传参数呢?为了适应各种不同参数的函数

import time
def timer(func):  # timer(test1)  func=test1
    def deco(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)
        stop_time = time.time()
        print("the func tun time is %s" %(stop_time-start_time))
    return deco  # 返回deco的内存地址
@timer
def test1():
    time.sleep(1)
    print("in the test1")
@timer
def test2(name):
    time.sleep(1)
    print("in the test2",name)

test1()
test2("fgf")

3、终极装饰器

注意,上面的例子中还没有涉及返回值,看下面的例子可以体会一下 
假设:公司网站需要验证登录,有不同的验证方式:本地认证、LDAP认证等

#/usr/bin/env python
# -*- coding: UTF-8 -*-
import time
user,passwd = 'fgf','abc123'
def auth(auth_type):
    print("auth func:",auth_type)
    def outer_wrapper(func):
        def wrapper(*args, **kwargs):
            print("wrapper func args:", *args, **kwargs)
            if auth_type == "local":
                username = input("Username:").strip()
                password = input("Password:").strip()
                if user == username and passwd == password:
                    print("\033[32;1mUser has passed authentication\033[0m")
                    res = func(*args, **kwargs)  # from home
                    print("---after authenticaion ")
                    return res
                else:
                    exit("\033[31;1mInvalid username or password\033[0m")
            elif auth_type == "ldap":
                print("搞毛线ldap,不会。。。。")

        return wrapper
    return outer_wrapper
def index():
    print("welcome to index page")
@auth(auth_type="local") # home = wrapper()
def home():
    print("welcome to home  page")
    return "from home"

@auth(auth_type="ldap")
def bbs():
    print("welcome to bbs  page")
index()
print(home()) #wrapper()
bbs()

到此这篇关于Python装饰器详细讲解的文章就介绍到这了,更多相关Python装饰器内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
python多进程操作实例
Nov 21 Python
python简单实现基于SSL的IRC bot实例
Jun 15 Python
python脚本实现xls(xlsx)转成csv
Apr 10 Python
浅析Python中yield关键词的作用与用法
Nov 29 Python
Python获取当前页面内所有链接的四种方法对比分析
Aug 19 Python
Python实现采用进度条实时显示处理进度的方法
Dec 19 Python
用Python画一个LinkinPark的logo代码实例
Sep 10 Python
Python:二维列表下标互换方式(矩阵转置)
Dec 02 Python
python实现单机五子棋
Aug 28 Python
详解Django自定义图片和文件上传路径(upload_to)的2种方式
Dec 01 Python
Pandas的数据过滤实现
Jan 15 Python
python正则表达式re.match()匹配多个字符方法的实现
Jan 27 Python
python中数组和列表的简单实例
Mar 25 #Python
Python if else条件语句形式详解
python中的getter与setter你了解吗
Mar 24 #Python
Python编程中内置的NotImplemented类型的用法
Mar 23 #Python
pandas进行数据输入和输出的方法详解
Mar 23 #Python
基于Python编写简易版的天天跑酷游戏的示例代码
Python中的嵌套循环详情
Mar 23 #Python
You might like
php中iconv函数使用方法
2008/05/24 PHP
PHP Zip解压 文件在线解压缩的函数代码
2010/05/26 PHP
PHP用身份证号获取星座和生肖的方法
2013/11/07 PHP
PHP截取指定图片大小的方法
2014/12/10 PHP
yii2.0实现验证用户名与邮箱功能
2015/12/22 PHP
php使用Jpgraph创建柱状图展示年度收支表效果示例
2017/02/15 PHP
PHP中的empty、isset、isnull的区别与使用实例
2019/03/22 PHP
JS脚本根据手机浏览器类型跳转WAP手机网站(两种方式)
2015/08/04 Javascript
jQuery中ajax的load()与post()方法实例详解
2016/01/05 Javascript
JavaScript实现弹出模态窗体并接受传值的方法
2016/02/12 Javascript
bootstrap datetimepicker日期插件超详细使用方法介绍
2017/02/23 Javascript
轻松理解JavaScript闭包
2017/03/14 Javascript
H5手机端多文件上传预览插件
2017/04/21 Javascript
Vue2.0基于vue-cli+webpack Vuex的用法(实例讲解)
2017/09/15 Javascript
解决layui追加或者动态修改的表单元素“没效果”的问题
2019/09/18 Javascript
layui 上传文件_批量导入数据UI的方法
2019/09/23 Javascript
简单了解JS打开url的方法
2020/02/21 Javascript
vue使用map代替Aarry数组循环遍历的方法
2020/04/30 Javascript
微信小程序实现菜单左右联动
2020/05/19 Javascript
JavaScript实现随机点名小程序
2020/10/29 Javascript
js实现随机点名功能
2020/12/23 Javascript
Python实现的Kmeans++算法实例
2014/04/26 Python
Python OpenCV实现图片上输出中文
2018/01/22 Python
python+logging+yaml实现日志分割
2019/07/22 Python
python zip()函数使用方法解析
2019/10/31 Python
Python中Yield的基本用法
2020/10/18 Python
html5 input元素新特性_动力节点Java学院整理
2017/07/06 HTML / CSS
澳洲国民品牌乡村路折扣店:Country Road & Trenery Outlet
2018/04/19 全球购物
AOP的定义以及作用
2013/09/08 面试题
公司庆典活动邀请函
2014/01/09 职场文书
竞聘书格式及范文
2014/03/31 职场文书
巾帼志愿者活动方案
2014/08/17 职场文书
教师个人年度总结
2015/02/11 职场文书
义诊活动通知
2015/04/24 职场文书
css3实现背景图片半透明内容不透明的方法示例
2021/04/13 HTML / CSS
ORACLE数据库应用开发的三十个注意事项
2021/06/07 Oracle