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解析树及树的遍历
Feb 03 Python
Linux中安装Python的交互式解释器IPython的教程
Jun 13 Python
python条件变量之生产者与消费者操作实例分析
Mar 22 Python
在PyCharm导航区中打开多个Project的关闭方法
Jan 17 Python
python实现支付宝转账接口
May 07 Python
浅谈Python中的字符串
Jun 10 Python
Python代码注释规范代码实例解析
Aug 14 Python
Python系统公网私网流量监控实现流程
Nov 23 Python
python爬虫中采集中遇到的问题整理
Nov 27 Python
在Django中使用MQTT的方法
May 10 Python
jupyter notebook保存文件默认路径更改方法汇总(亲测可以)
Jun 09 Python
python单向链表实例详解
May 25 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 操作excel文件的方法小结
2009/12/31 PHP
php数字转汉字代码(算法)
2011/10/08 PHP
php判断str字符串是否是xml格式数据的方法示例
2017/07/26 PHP
jsPDF导出pdf示例
2014/05/02 Javascript
js加入收藏夹代码(兼容ie/ff/op)
2014/05/16 Javascript
Javascript中的五种数据类型详解
2014/12/26 Javascript
深入理解js函数的作用域与this指向
2016/05/28 Javascript
jQuery动态修改字体大小的方法【测试可用】
2016/09/09 Javascript
JS实现隔行换色的表格排序
2017/03/27 Javascript
微信小程序使用slider设置数据值及switch开关组件功能【附源码下载】
2017/12/09 Javascript
Node.js的Koa实现JWT用户认证方法
2018/05/05 Javascript
使用vuex缓存数据并优化自己的vuex-cache
2018/05/30 Javascript
JS中数组与对象的遍历方法实例小结
2018/08/14 Javascript
JS异步执行结果获取的3种解决方式
2019/02/19 Javascript
详解js动态获取浏览器或页面等容器的宽高
2019/03/13 Javascript
JSON是什么?有哪些优点?JSON和XML的区别?
2019/04/29 Javascript
webpack项目使用eslint建立代码规范实现
2019/05/16 Javascript
如何在VUE中使用vue-awesome-swiper
2021/01/04 Vue.js
[49:27]2018DOTA2亚洲邀请赛 4.4 淘汰赛 TNC vs VG 第一场
2018/04/05 DOTA
分析在Python中何种情况下需要使用断言
2015/04/01 Python
在Python的Django框架中simple-todo工具的简单使用
2015/05/30 Python
Python使用minidom读写xml的方法
2015/06/03 Python
wxPython定时器wx.Timer简单应用实例
2015/06/03 Python
python装饰器-限制函数调用次数的方法(10s调用一次)
2018/04/21 Python
Python3实现计算两个数组的交集算法示例
2019/04/03 Python
如何使用python进行pdf文件分割
2019/11/11 Python
pytorch程序异常后删除占用的显存操作
2020/01/13 Python
德国骆驼商店:ActiveFashionWorld
2017/11/18 全球购物
Farfetch美国:奢侈品牌时尚购物平台
2019/05/02 全球购物
医校毕业生自我鉴定
2014/01/25 职场文书
经典公益广告词
2014/03/13 职场文书
四风批评与自我批评范文
2014/10/14 职场文书
老公写给老婆的检讨书
2015/05/06 职场文书
2016大学生社会实践心得体会范文
2016/01/14 职场文书
解决tk mapper 通用mapper的bug问题
2021/06/16 Java/Android
Python可视化神器pyecharts绘制水球图
2022/07/07 Python