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 ORM框架SQLAlchemy学习笔记之关系映射实例
Jun 10 Python
Python实现冒泡,插入,选择排序简单实例
Aug 18 Python
python获取指定目录下所有文件名列表的方法
May 20 Python
python简单线程和协程学习心得(分享)
Jun 14 Python
python2.7安装图文教程
Mar 13 Python
为何人工智能(AI)首选Python?读完这篇文章你就知道了(推荐)
Apr 06 Python
django认证系统 Authentication使用详解
Jul 22 Python
python 实现二维字典的键值合并等函数
Dec 06 Python
Python生成个性签名图片获取GUI过程解析
Dec 16 Python
python求最大公约数和最小公倍数的简单方法
Feb 13 Python
Python双链表原理与实现方法详解
Feb 22 Python
python和node.js生成当前时间戳的示例
Sep 29 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
信用卡效验程序
2006/10/09 PHP
一步一步学习PHP(6) 面向对象
2010/02/16 PHP
PHP文件打开、关闭、写入的判断与执行代码
2011/05/24 PHP
Smarty中调用FCKeditor的方法
2014/10/27 PHP
完整显示当前日期和时间的JS代码
2007/09/17 Javascript
Chrome中模态对话框showModalDialog返回值问题的解决方法
2010/05/25 Javascript
兼容IE和Firefox火狐的上下、左右循环无间断滚动JS代码
2013/04/19 Javascript
浏览器兼容console对象的简要解决方案分享
2013/10/24 Javascript
js的参数有长度限制吗?发现不能超过2083个字符
2014/04/20 Javascript
JavaScript数组对象赋值用法实例
2015/08/04 Javascript
Bootstrap table使用方法详细介绍
2016/12/09 Javascript
nodeJS删除文件方法示例
2016/12/25 NodeJs
javascript实现简易计算器
2017/02/01 Javascript
深入浅析AngularJs模版与v-bind
2018/07/06 Javascript
vue-cli2 构建速度优化的实现方法
2019/01/08 Javascript
layer关闭弹出窗口触发表单提交问题的处理方法
2019/09/25 Javascript
浅谈vue 二级路由嵌套和二级路由高亮问题
2020/08/06 Javascript
Python文件夹与文件的操作实现代码
2014/07/13 Python
python统计一个文本中重复行数的方法
2014/11/19 Python
用实例详解Python中的Django框架中prefetch_related()函数对数据库查询的优化
2015/04/01 Python
python生成随机密码或随机字符串的方法
2015/07/03 Python
深入浅析Python传值与传址
2018/07/10 Python
基于Python实现用户管理系统
2019/02/26 Python
python基于FTP实现文件传输相关功能代码实例
2019/09/28 Python
Python数据分析pandas模块用法实例详解
2019/11/20 Python
CSS3实现点击放大的动画实例代码
2017/02/27 HTML / CSS
CSS3 实现倒计时效果
2020/11/25 HTML / CSS
aden + anais英国官网:美国婴儿贴身用品品牌
2019/09/08 全球购物
工程部经理岗位职责
2013/12/08 职场文书
中学教师管理制度
2014/01/14 职场文书
教师研修随笔感言
2014/01/23 职场文书
竞选班长自荐书范文
2014/03/09 职场文书
2015年个人实习工作总结
2015/05/28 职场文书
家长必看:义务教育,不得以面试 评测等名义选拔学生
2019/07/09 职场文书
导游词之桂林
2019/08/20 职场文书
Python 解决空列表.append() 输出为None的问题
2021/05/23 Python