Python面试——装饰器

news2024/11/15 7:41:26

知识链接:
在这里插入图片描述

装饰器

装饰器可调用的对象,其参数是被装饰的函数。装饰器可能会处理被装饰的函数然后把它返回,或者将其替换成另外一个函数或者可调用对象。
装饰器有两大特性:

  • 能把被装饰的函数替换成其他函数(在元编程时,这样更方便 --> 在运行时改变程序的行为)
  • 装饰器在加载模块时立即执行

把被装饰的函数替换成其它函数

这样描述可能比较抽象,我们可以从一个例子来看下其特性:如何去计算函数执行的时间?
处理思路常规的处理思路是:在函数执行开始与结束时分别记录时间,并计算差值(执行时间)。这样处理有好处,也有不便的地方。
好处:如果是计算单个函数,修改简单易处理。
不便:会修改整个函数的执行逻辑,且在大量需要计算函数执行时间的地方不方便统一修改。
我们可以通过装饰器来进行处理:

import time

def fun1():
	time.sleep(3)  # 模拟函数执行过程

def timmer(func):
	def inner(*args, **kwargs):
  	start_time = time.time()
  	func(*args, **kwargs)
  	end_time = time.time()
    print("函数执行时间为:", end_time - start_time)

	return inner

fun1 = timmer(fun1)
fun1()  # 函数执行时间为: 3.0051169395446777

上诉代码的执行效果与下述写法一样:

import time

def timmer(func):
	def inner(*args, **kwargs):
  	start_time = time.time()
  	func(*args, **kwargs)
  	end_time = time.time()
    print("函数执行时间为:", end_time - start_time)

	return inner

@timmer
def fun1():
	time.sleep(3)  # 模拟函数执行过程

fun1()  # 函数执行时间为: 3.0051169395446777

这样做有一个小问题,就是被装饰的函数的__name__和__doc__属性被遮盖了。
可以通过使用functools.wraps 装饰器把相关属性从fun复制到timmer中:

import time
import functools

def timmer(func):
	@functools.wraps(func)
	def inner(*args, **kwargs):
  	start_time = time.time()
  	func(*args, **kwargs)
  	end_time = time.time()
    print("函数执行时间为:", end_time - start_time)

	return inner

@timmer
def fun1():
	time.sleep(3)  # 模拟函数执行过程

模块加载时立即执行

再通过一个例子来理解装饰器会在模块加载时立即执行:

def register(func):
    print(f"running decorator...register: {func}")
    return func


@register
def fun1():
    print("running func1")


@register
def fun2():
    print("running fun2")


def fun3():
    print("running fun3")


if __name__ == '__main__':
    fun1()
    fun2()
    fun3()
------------------------------
running decorator...register: <function fun1 at 0x7f7900088280>
running decorator...register: <function fun2 at 0x7f7900096550>
running func1
running fun2
running fun3


可以看出:装饰器在导入模块时立即执行,而被装饰的函数只有在明确调用时才会运行。这就突出了在Python中导入时和运行时的区别。

另外,如果存在多层装饰器时,导入时的执行顺序是由内到外,运行时的执行顺序是由外到内:

def war1(func):
    print("running war1")

    def inner1(*args, **kwargs):
        print("====inner1====")
        print(f'inner1中参数func:{func}')
        func(*args, **kwargs)

    return inner1


def war2(func):
    print("running war2")

    def inner2(*args, **kwargs):
        print("====inner2====")
        print(f'inner2中参数func:{func}')
        func(*args, **kwargs)

    return inner2


@war2
@war1
def fun1():
    print("running func1")


# --> fun1 = war2(war1(fun1))

if __name__ == '__main__':
    fun1()


------------------------------------
running war1
running war2
====inner2====
inner2中参数func:<function war1.<locals>.inner at 0x7fad681a54c0>
====inner1====
inner1中参数func:<function fun1 at 0x7fad680e6550>
running func1

变量作用域

在理解闭包之前,我们需要先了解下变量作用域。
先来看第一个例子,这段代码很简单也很容易理解:

b = 3

def fun():
    a = 1
    print(a)  # 获取局部变量并打印
    print(b)  # 获取全局变量并打印

fun()
--------------------------------
1
3

下面,再坐下简单的修改:

b = 3

def fun():
    a = 1
    print(a)
    print(b)
    b = 9

fun()
------------------
UnboundLocalError: local variable 'b' referenced before assignment
1

在示例一的基础上,增加一行为b赋值,可是在执行print(b)时却报错了,但是输出了1,这表明print(a)是正常执行的。为什么有全局变量b,且是在print(b)之后进行赋值操作的,为什么会报错没有声明就引用的错误了呢?

我们可以通过比较两个示例函数的字节码来找到原因:

import dis
print(dis.dis(fun))
-----------------------------------------------------
  7           0 LOAD_CONST               1 (1)
              2 STORE_FAST               0 (a)

  8           4 LOAD_GLOBAL              0 (print)
              6 LOAD_FAST                0 (a)
              8 CALL_FUNCTION            1
             10 POP_TOP

  9          12 LOAD_GLOBAL              0 (print)
             14 LOAD_GLOBAL              1 (b)
             16 CALL_FUNCTION            1
             18 POP_TOP
             20 LOAD_CONST               0 (None)
             22 RETURN_VALUE
None

import dis
print(dis.dis(fun))
-----------------------------------------------------
	7           0 LOAD_CONST               1 (1)
              2 STORE_FAST               0 (a)

  8           4 LOAD_GLOBAL              0 (print)
              6 LOAD_FAST                0 (a)
              8 CALL_FUNCTION            1
             10 POP_TOP

  9          12 LOAD_GLOBAL              0 (print)
             14 LOAD_FAST                1 (b)
             16 CALL_FUNCTION            1
             18 POP_TOP

 10          20 LOAD_CONST               2 (9)
             22 STORE_FAST               1 (b)
             24 LOAD_CONST               0 (None)
             26 RETURN_VALUE
None

从字节码中可以看出Cpython解释器在编译示例二中的函数时,把b视作局部变量(因为在函数中为b赋值了),即使在print(b)后面才为b赋值,因为变量的种类(是不是局部变量)不能改变函数的定义体。

global

有没有一种方法可以在示例二中为全局变量赋值,又不会导致解释器报错的呢?
Python提供了global关键字,可以在函数体中声明变量为全局变量:

import dis

b = 3


def fun():
    global b
    a = 1
    print(a)
    print(b)
    b = 9
  	print(b)

if __name__ == '__main__':
    fun()
    print(dis.dis(fun))
----------------------------
1 
3
9

打印其字节码可以看到,Cpython解释器已经把b视为全局变量了:

  8           0 LOAD_CONST               1 (1)
              2 STORE_FAST               0 (a)

  9           4 LOAD_GLOBAL              0 (print)
              6 LOAD_FAST                0 (a)
              8 CALL_FUNCTION            1
             10 POP_TOP

 10          12 LOAD_GLOBAL              0 (print)
             14 LOAD_GLOBAL              1 (b)
             16 CALL_FUNCTION            1
             18 POP_TOP

 11          20 LOAD_CONST               2 (9)
             22 STORE_GLOBAL             1 (b)
             24 LOAD_CONST               0 (None)
             26 RETURN_VALUE
None

闭包

闭包指延展了作用域的函数,其中包含函数定义体中的引用,但是不在定义体中定义的非全局变量。

在装饰器的示例中,存在一个疑问:func是timmer函数的局部变量,在timmer(func1)执行完后,其本地作用域也会清除,为什么在执行func1()时可以获取到对应的func参数呢?

import time

def fun1():
	time.sleep(3)

def timmer(func):
	def inner(*args, **kwargs):
  	start_time = time.time()
  	func(*args, **kwargs)
  	end_time = time.time()
    print("函数执行时间为:", end_time - start_time)

	return inner

fun1 = timmer(fun1)
fun1()

在inner函数中,func是自由变量(free varialbe),指未在本地作用域中绑定的变量。审查fun1的__code__属性可以看到这些值,自由变量的值可以在__closure__中查看:

>>> fun1.__code__.co_varnames
('args', 'kwargs', 'start_time', 'end_time')
>>> func1.__code__.co_freevars
('func',)
>>> fun1.__closure__[0].cell_contents
<function fun1 at 0x7fc0580e8040>

nonlocal

我们接下来再看一个例子,通过闭包来计算移动的平均值的高阶函数:

def make_average():
    total_step = 0  # 移动总数
    count = 0  # 移动次数

    def average(new_step):
        count += 1
        total_step += new_step
        return total_step / count

    return average


if __name__ == '__main__':
    avg = make_average()
    print(avg(10))  # 模拟移动
    print(avg(20))
-------------------------------------
UnboundLocalError: local variable 'count' referenced before assignment

通过对闭包的学习,这段代码应该没问题,但是执行却报错了。正常来说count和total_step都应该是自由变量,为什么会被Cpython解释器视为局部变量呢【在上一节可以找到答案】?
为了解决这个问题,Python3引入了nonlocal声明,它的作用是把变量声明为自由变量,即使如果为闭包中绑定的自由变量赋予了新值则会更新绑定:

def make_average():
    total_step = 0  # 移动总数
    count = 0  # 移动次数

    def average(new_step):
      	nonlocal count, total_step
        count += 1
        total_step += new_step
        return total_step / count

    return average


if __name__ == '__main__':
    avg = make_average()
    print(avg(10))  # 模拟移动
    print(avg(20))
---------------------------------------
10.0
15.0

查看字节码可以看到count和被声明为自由变量:

  7           0 LOAD_DEREF               0 (count)
              2 LOAD_CONST               1 (1)
              4 INPLACE_ADD
              6 STORE_DEREF              0 (count)

  8           8 LOAD_DEREF               1 (total_step)
             10 LOAD_FAST                0 (new_step)
             12 INPLACE_ADD
             14 STORE_DEREF              1 (total_step)

  9          16 LOAD_DEREF               1 (total_step)
             18 LOAD_DEREF               0 (count)
             20 BINARY_TRUE_DIVIDE
             22 RETURN_VALUE
None

函数是一等对象

在Python中,函数是一等对象:

  • 在运行时被创建
  • 能赋值给变量或数据结构中的元素
  • 能作为参数传递给函数
  • 能做为函数的返回结果
  • 后面亮点可以为我们理解闭包提供帮助

装饰器面试题

  • 装饰器基础知识考察

  • 装饰器作用

  • 装饰器的原理和实现

  • 装饰器存在的缺陷

  • 工作中是否有用到装饰器(Python内置的或第三方封装的)

  • 内置

  • property、classmethod、staticmethod

  • functools.wraps

  • functools.lru_cache

  • functools.singledispatch

  • 第三方

  • Django的csrf_exempt

  • 手写装饰器

  • 装饰器进阶

  • 导入时和运行时

  • 多层装饰器执行顺序

  • 自由变量

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/343190.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

面试腾讯测试岗后感想,真的很后悔这5年一直都干的是基础测试....

前两天&#xff0c;我的一个朋友去大厂面试&#xff0c;跟我聊天时说&#xff1a;输的很彻底… 我问她&#xff1a;什么情况&#xff1f;她说&#xff1a;很后悔这5年来一直都干的是功能测试… 相信许多测试人也跟我朋友一样&#xff0c;从事了软件测试很多年&#xff0c;却依…

树莓派用默认账号和密码登录不上怎么办;修改树莓派的密码

目录 一、重置树莓派的默认账号和密码 二、修改树莓派的密码 三、超级用户和普通用户的切换 一、重置树莓派的默认账号和密码 在SD卡中根目录建立文件userconf 在userconf中输入如下内容&#xff1a; pi:$6$/4.VdYgDm7RJ0qM1$FwXCeQgDKkqrOU3RIRuDSKpauAbBvP11msq9X58c8Q…

STM32开发(10)----CubeMX配置基本定时器

CubeMX配置基本定时器前言一、定时器的介绍二、实验过程1.实验材料2.STM32CubeMX配置基本定时器2.代码实现3.编译烧录4.硬件连接5.实验结果总结前言 本章介绍使用STM32CubeMX对基本定时器进行配置的方法&#xff0c;STM32F103高性能系列设备包括基本定时器、高级控制定时器、通…

JavaEE-HTTP协议(一)

目录什么是HTTP协议&#xff1f;协议格式如何看到HTTP的报文格式&#xff1f;HTTP请求HTTP响应URLURL encode/decode什么是HTTP协议&#xff1f; 计算机网络&#xff0c;核心概念&#xff0c;网络协议 网络协议种类非常多&#xff0c;其中一些耳熟能详的&#xff0c;IP,TCP,UD…

shell命令行并行神器 - parallel

shell命令行并行神奇 - parallel 概述 GNU parallel 是一个 shell 工具&#xff0c;用于使用一台或多台计算机并行执行作业。作业可以是单个命令或必须为输入中的每一行运行的小脚本。典型的输入是文件列表、主机列表、用户列表、URL 列表或表列表。作业也可以是从管道读取的…

98年的确实卷,公司新来的卷王,我们这帮老油条真干不过.....

都说00后躺平了&#xff0c;但是有一说一&#xff0c;该卷的还是卷。这不&#xff0c;前段时间我们公司来了个00后&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。 …

电脑麦克风没声音怎么办?这3招就可以解决!

最近有用户在使用电脑麦克风进行视频录制时&#xff0c;发现麦克风没有声音。这是什么原因&#xff1f;电脑麦克风没有声音怎么办&#xff1f;关于解决方案&#xff0c;我专门整理了三种方法来帮你们&#xff0c;一起来看看吧&#xff01; 操作环境&#xff1a; 演示机型&#…

在TitanIDE中使用ChatGPT辅助科研开发

作者&#xff1a;行云创新CEO 马洪喜 命题&#xff1a;太空望远镜拍摄的照片处理 假设&#xff1a;我是图形科学家&#xff0c;但不是特别懂Python 先上传一张银河系照片&#xff0c;目的是把彩色转成灰度&#xff1a; 然后我不会啊&#xff0c; 问问chatGPT 彩色图片转灰度…

电话号码的字母组合-力扣17-java

一、题目描述给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。示例 1&#xff1a;输入&#xff1a;digits "23"输出…

Android 一体机研发之修改系统设置————自动锁屏

Android 一体机研发之修改系统设置————屏幕亮度 Android 一体机研发之修改系统设置————声音 Android 一体机研发之修改系统设置————自动锁屏 修改系统设置系列篇章马上开张了&#xff01; 本章将为大家细节讲解自动锁屏。 自动锁屏功能&#xff0c;这个可以根据…

简述springIOC容器的bean加载流程

参考笔记:https://blog.51cto.com/u_14006572/3118363 https://zhuanlan.zhihu.com/p/386335813 https://blog.csdn.net/mrathena/article/details/115654379 目录结构 spring ioc容器的加载&#xff0c;大体上经过以下几个过程&#xff1a; 资源文件定位、解析、注册、实例化…

UWA Pipeline 2.4.1 版本更新说明

UWA Pipeline是一款面向游戏开发团队的本地协作平台&#xff0c;旨在为游戏开发团队搭建专属的DevOps研发交付流水线&#xff0c;提供可视化的CICD操作界面、高可用的自动化测试以及UWA性能保障服务的无缝贴合等实用功能。 在本次UWA Pipeline 2.4.1版本更新中&#xff0c;主要…

PIL.Image与cv2之间的常用API汇总

简单介绍 主要是因为经常用到这两个&#xff0c;经常弄混淆&#xff0c;所以&#xff0c;总结一番。持续更新。 from PIL import Image import cv2 as cv import numpy as np import matplotlib.pyplot as plt1、读取文件与写入文件 1.1 Image.open() img_pil Image.open…

电商数据监测——中国白酒行业数据浅析

大国盛世酿,万家潭酒香。中国白酒是中国特色文化之一。 2022年&#xff0c;国内白酒总产量为671.2万千升&#xff0c;处于持续下滑的态势。 白酒产量不佳&#xff0c;但线上平台的销售情况却成绩优异。2022年&#xff0c;京东平台白酒的年度总销量超3500万件&#xff0c;同比去…

操作系统开发:编写开机引导

操作系统是用来管理与协调硬件工作的&#xff0c;开发一款操作系统有利于理解底层的运转逻辑&#xff0c;本篇内容主要用来理解操作系统是如何启动的&#xff0c;又是如何加载磁盘中的内核的&#xff0c;该系列文章参考各类底层书籍&#xff0c;通过自己的理解并加以叙述&#…

彻底理解 cookie、session、token (一)

发展史 1、很久很久以前&#xff0c;Web 基本上就是文档的浏览而已&#xff0c; 既然是浏览&#xff0c;作为服务器&#xff0c; 不需要记录谁在某一段时间里都浏览了什么文档&#xff0c;每次请求都是一个新的HTTP协议&#xff0c; 就是请求加响应&#xff0c; 尤其是我不用记…

阿里巴巴在开源压测工具 JMeter 上的实践和优化

Apache JMeter [1] 是 Apach 旗下的开源压测工具&#xff0c;创建于 1999 年初&#xff0c;迄今已有超过 20 年历史。JMeter 功能丰富&#xff0c;社区&#xff08;用户群体&#xff09;庞大&#xff0c;是主流开源压测工具之一。 性能测试通常集中在新系统上线或大型活动前&…

l1和l2接口如何进行编写?一定要掌握这几个元素

在这个大数据时代&#xff0c;很多地方都需要用到l1和l2接口&#xff0c;l1和l2接口在应用程序与数据库之间起着桥梁的作用&#xff0c;是实现数据的整合与共享的重要帮手。 l1和l2接口适用于各行各业&#xff0c;应用场景的不断拓展&#xff0c;l1和l2接口的发展也兴起&#…

浏览器广告拦截插件| 浏览器搜索广告横飞怎么办

文章目录浏览器广告拦截插件| 浏览器搜索广告横飞怎么办一、效果二、安装浏览器广告拦截插件| 浏览器搜索广告横飞怎么办 浏览器广告横飞怎么办&#xff1f;今天教你一招解决&#xff01;很多小伙伴说自己用的浏览器总是有广告。 今天咱们就针对这个问题分享一个浏览器插件&a…

【面试题】JavaScript中递归的理解

大厂面试题分享 面试题库后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★地址&#xff1a;前端面试题库递归 RecursionTo iterate is human, to recurse, divine. 理解迭代&#xff0c;神理解递归。本文会以 JavaScript为主、有部分 Rust 举例说明。…