当前位置: 首页 > news >正文

深入理解python装饰器

前言

你学习python的时候一定也遇到了装饰器,但是很多学习教程对装饰器的解释都是绕来绕去的,把初学者都给绕的云里雾里,我想读者你一定有类似的经历对吧?不过不要着急,只要你仔细用心的观看我这篇文章之后,你对python装饰器就完全理解了。相信我,我是想将复杂的问题简单的讲解出来,分享给更多的人,如果还有什么问题,可以留言告诉我,我们一起进步。

基础知识

装饰器,其实就是将一个函数作为参数传递给另外一个函数,先来看一段代码

def before_hi(func):
    print("before executing hi()")
    func()

def hi():
    print("hi changpzh!")

before_hi(hi)

#outputs:
# before executing hi()
# hi changpzh!

解释:

其实这段代码对大多数人来说应该都是能轻易的看的懂的。 

1. 首先定义了两个函数,一个是hi,一个是before_hi,before_hi接受一个函数作为参数。

2. 所以执行before_hi(hi),意思就是将hi这个函数作为参数传递给了before_hi

3. 因此打印会是先before hi(),然后是hi

但是,他和我们的装饰器有什么关系呢?别慌,听我慢慢道来

我先把上面的函数做简单的改造, before_hi函数做了一层封装,此时返回的就是一个封装后的函数wrap_function了。

def before_hi(a_func):
    def wrap_function():
        print("before executing hi()")
        a_func()
    return wrap_function


def hi():
    print("hi changpzh!")

hi = before_hi(hi)

hi()
# outputs:
# before executing hi()
# hi changpzh!

解释:

1. 首先当hi = before_hi(hi)执行的时候,实际上是将hi这个函数作为参数传递给了before_hi,然后在before_hi中运行,before_hi此时返回一个wrap_function回去。

2. hi()相当于在运行wrap_function()了。然后进行了对应的打印

当然此时看起来还是和我们的装饰器没有一点关系,不过再看我对上面代码进行一些变化,以便让你真正的理解装饰器。我用装饰器来代替其中一行代码用`@before_hi 代替 hi = before_hi(hi)`,看以下代码

def before_hi(a_func):
    def wrap_function():
        print("before executing hi()")
        a_func()
    return wrap_function

@before_hi            # 注意位置 等价于 hi = before_hi(hi)
def hi():
    print("hi changpzh!")


hi()
# outputs:
# before executing hi()
# hi changpzh!

解释:

这里代码和之前代码只有很小的区别,就是 @before_hi 代替 hi = before_hi(hi)。但是执行之后得到的效果却是一样的。

说明装饰器@的作用,就是将函数作为参数传递给装饰器函数。

也就是说在这里 @before_hi 等价 hi = before_hi(hi),函数hi就被wrap_function代替了。

装饰器深入理解

我们再来进一步了解装饰器,通过hi.__name__可以看到输出结果为wrap_function,参看下图,这里的hi函数名,被wrap_function代替了,这不是我们想要的,我们想要的是hi.__name__输出为hi。

那么我们怎么办呢? 不慌,好在python提供了一个简单的函数来解决这个问题,那就是functiontools.wraps,我们修改上面例子,通过使用functiontools.wraps,代码如下

from functools import wraps

def before_hi(a_func):
    @wraps(a_func)
    def wrap_function():
        print("before executing hi()")
        a_func()
    return wrap_function

@before_hi
def hi():
    print("hi changpzh!")


# hi()
print(hi.__name__)
# outputs:
# hi

此时的hi.__name__就是hi了,看下图调试结果。

当然如果不用functiontools.wraps,还会改变函数的注释文档,所以建议用装饰器时,使用functiontools.wraps

装饰器的使用场景

授权

装饰器能有助于检查某个⼈是否被授权去使⽤⼀个web应⽤的端点(endpoint)。它们被⼤量
使⽤于Flask和Django web框架中。这⾥是⼀个例⼦来使⽤基于装饰器的授权

from functools import wraps
def requires_auth(func):
    @wraps(func)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            authenticate()
        return func(*args, **kwargs)
    return decorated

 日志

⽇志是装饰器运⽤的另⼀个亮点,举例

from functools import wraps

def logit(logfile='out.log'):
    def logging_decorator(func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called" + "args is" + str(args) + " kwargs is " + str(kwargs)
            with open(logfile, mode='a') as opened_file:
                opened_file.write(log_string + '\n')

        return wrapped_function
    return logging_decorator


@logit()
def func1():
    print("test")

@logit('func2.log')
def func2():
    pass

func1("first", 2, age=30, name="changpzh")
func2()

解释:

注意这里的装饰器使用,和之前的有很大的一个差异,在于,之前使用是@logit,但是这里是@logit(),这里多了一个括号,意味着代码运行到这里的时候,实际上是执行了一次函数logit的。

所以:代码运行过程中

1. 当运行到@logit()时,首先执行logit函数,返回了logging_decorator这个装饰器函数

2. 因此,在运行接下来的函数def func1()时,执行了logging_decorator(func),并且返回wrapped_function给func1. 此时func1就等价为wrapped_function了。

3. 运行func1时,就是运行wrapped_function,传入的参数就会给到*args, **kwargs。结果如下所示。

4. 同理运行func2

装饰器类实现

from datetime import datetime


class Logit(object):
    def __init__(self, logfile='out.log'):
        self.logfile = logfile

    def __call__(self, func):  # 在函数定义的时候调用
        # 打印日志
        self.print_log(func)
        return func

    def print_log(self, func):
        log_string = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f ") + func.__name__ + " was called"
        print(log_string)
        # 打开logfile并写⼊
        with open(self.logfile, 'a') as opened_file:
            # 现在将⽇志打到指定的⽂件
            opened_file.write(log_string + '\n')



@Logit()    # 这里会调用 __init__
def func1():  # 这里会调用 __call__
    print(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f ")} func1 was really called')
    pass


@Logit('func2.log')
def func2():
    print(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f ")} func2 was really called')
    pass


func1()
func2()

运行结果如下所示:

好了,今天的装饰器就讲解到这里,相信你看完这篇文章之后,对装饰的运行原理应该很清楚了。再次遇到装饰器时,你也有足够的信心相信自己可以征服她了。

相关文章:

  • 大数据趣味学习探讨(三):怎么确定学习目标
  • SSM整合(超详细)
  • 【程序语言】-- 编程语言分类和应用
  • Springboot三层架构--DAO层、Service层、Colltroler层--这波我在外太空
  • Selenium快速入门
  • manim|集合的运算
  • 【代理设计模式 Objective-C语言】
  • 【C++】类和对象 (上篇)
  • 【Shell编程】Bash变量-用户自定义变量
  • MySQL经典练习题+解题思路(三)
  • 基于STM32的TM1638的按键控制以及数码管和LED灯的动态扫描
  • 【Linux】Tomcat优化
  • 10.4复习
  • LVGL v8学习笔记 | 09 - 进度条控件的使用方法(bar)
  • 力扣239 - 滑动窗口的最大值【单调队列的原理】
  • 【vuex入门系列02】mutation接收单个参数和多个参数
  • CentOS 7 修改主机名
  • Java读取Properties文件的六种方法
  • Lsb图片隐写
  • MD5加密原理解析及OC版原理实现
  • mongodb--安装和初步使用教程
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • Redux 中间件分析
  • Spring Boot快速入门(一):Hello Spring Boot
  • Vue.js-Day01
  • Windows Containers 大冒险: 容器网络
  • 从0到1:PostCSS 插件开发最佳实践
  • 分布式任务队列Celery
  • 关于使用markdown的方法(引自CSDN教程)
  • 爬虫模拟登陆 SegmentFault
  • 排序(1):冒泡排序
  • 我是如何设计 Upload 上传组件的
  • 小程序button引导用户授权
  • 《TCP IP 详解卷1:协议》阅读笔记 - 第六章
  • # Maven错误Error executing Maven
  • #### go map 底层结构 ####
  • $con= MySQL有关填空题_2015年计算机二级考试《MySQL》提高练习题(10)
  • ( 用例图)定义了系统的功能需求,它是从系统的外部看系统功能,并不描述系统内部对功能的具体实现
  • (Ruby)Ubuntu12.04安装Rails环境
  • (附源码)springboot 校园学生兼职系统 毕业设计 742122
  • (附源码)springboot太原学院贫困生申请管理系统 毕业设计 101517
  • (附源码)ssm航空客运订票系统 毕业设计 141612
  • (强烈推荐)移动端音视频从零到上手(上)
  • (三)mysql_MYSQL(三)
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (一)SpringBoot3---尚硅谷总结
  • (转载)深入super,看Python如何解决钻石继承难题
  • @CacheInvalidate(name = “xxx“, key = “#results.![a+b]“,multi = true)是什么意思
  • @GlobalLock注解作用与原理解析
  • @Repository 注解
  • @SuppressWarnings注解
  • [bzoj1324]Exca王者之剑_最小割
  • [BZOJ2281][SDOI2011]黑白棋(K-Nim博弈)
  • [codevs 1515]跳 【解题报告】
  • [CSS]CSS 的背景