Python【修饰器/装饰器】

news2024/9/23 17:21:39

Python【修饰器/装饰器】

修饰器(装饰器)在Python中也是一个很重要的内容,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,相当于一个语法糖,可能在新手看来,这是一个难以理解或者不知道有啥用的存在,但后面你多多少少都要碰到或者用到它的。

在这里插入图片描述


一、修饰器的概念和作用

1.什么是修饰器?

修饰器又叫装饰器,本身也是一个函数,是在原有的函数或者是方法上增添一些额外的功能。


2.修饰器的作用

概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能

比如说这个函数是注册的功能,但有时候这个用户在执行这个操作的时候,他是已注册的用户,我这个函数已经写好了,不想动它了,那么我们就可以通过修饰器来给这个函数增加一个登录的功能。

它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

在这里插入图片描述

修饰器的具体操作,我们来慢慢学习。


二、修饰器的使用

1.使用说明

在使用修饰器之前,我们得记住几个关于修饰器的使用说明:

(1)修饰器的关键字是 @ ,Python代码中只要出现了它,你就可以想到是修饰器了。

(2)修饰器修饰的是函数或者是方法,不能修饰一个类

(3)修饰器必须出现在被修饰函数或者方法的前一行,不能够将修饰器定义在函数的同行

例子:

在这里插入图片描述

虽然修饰器本身是一个函数,但它的出现是有规定的,我上面的修饰器就没有出现在被修饰函数或者方法的前1行,所以连 print(“龙叔”) 这一行代码都执行不了。

(4)修饰器本身是一个函数,将被修饰的函数作为参数,传递给修饰器,执行修饰器中功能,返回传递进来的函数对象,调用返回出来的函数

这几个点很重要,下面我们通过修饰器的多种使用方式来加深理解。

在这里插入图片描述


2.基本使用方式

如果被修饰的函数不调用,则执行@后面的函数,并把被修饰的函数当做参数传递过去了,则修饰器函数的返回值可以是任意值

例子:

def dec(a):		#修饰器函数
    print(a)	#看一下形参传了什么
    print("helloworld")
    return None

@dec	#使用修饰器

def funA():		#被修饰的函数
    pass

运行结果:

<function funA at 0x0000018D849BB670>
helloworld

首先我们可以看出来,这就是很简单的一个修饰器的使用例子,用 @dec 来调用dec()修饰器函数,而被修饰的函数funA()是没有进行什么操作的。

其次我们再看,被修饰的函数funA()不调用什么,但却被当做参数传递给了修饰器函数dec(),所以dec()需要一个形参来接受传递值,我用的是a作为形参,如果你去掉a的话,系统就会报错,因为被修饰的函数funA()返回的值没有东西接受,你可以试试。

最后还有1点,被修饰函数funA()并没有被调用,注意到了吗?所以修饰器函数dec()返回什么都可以,上面返回的是None,你返回一个“有病”也可以的,返回什么都可以的,下面我们再看被修饰函数 funA() 被调用的情况。

在这里插入图片描述

如果被修饰的函数调用了,直接执行修饰器,并把被修饰的函数当做参数专递给修饰器,但是修饰器的返回值必须是当前参数

例子:

def dec(a):
    print(a)
    print("helloworld")
    return "有病"

@dec	#使用修饰器函数

def funA():
    pass

funA()	#调用被修饰函数

运行结果:

<function funA at 0x000001F78A48B670>
helloworld
Traceback (most recent call last):
  File "E:\Python-learn\可迭代对象\修饰器\修饰器_test.py", line 11, in <module>
    funA()
TypeError: 'str' object is not callable

出现错误了,说字符串str不能被迭代,为什么?其实原因就是有修饰器存在并使用的情况下,被修饰函数又被调用了,这个时候返回值就不能是任意值了,此时的修饰器函数只能返回传递值,你可以试试把 return “有病” 改成 return a 就可以正常输出了,又或者你去掉 funA() 这一行代码,不调用被修斯函数,则输出也是正常的。

def dec(a):
    print(a)
    print("helloworld")
    return a

@dec

def funA():
    pass

funA()

运行结果:

<function funA at 0x0000020F5664B670>
helloworld

一切正常。

我们再来看看修饰器的执行逻辑是怎么样的:

def dec(a):
    print(a)
    print("修饰器函数")
    return a

@dec

def funA():
    print("被修饰函数")

funA()

print("龙叔")

运行结果:

<function funA at 0x000001D90E75B670>
修饰器函数
被修饰函数
龙叔

从这个运行结果我们可以看出修饰器的运行逻辑是:被修饰的函数 funA() 被调用了,但却没有被直接执行,而是直接执行修饰器dec,并把被修饰的函数 funA() 当做参数专递给修饰器dec,执行修饰函数dec()里面的代码,返回传递值a后才执行的被修饰函数funA()里面的代码,最后走完了被修饰函数funA() 才执行剩余的代码。

我用个比较草一点的图来表示一下:

在这里插入图片描述


3.其他使用方式:函数嵌套

前面我们讲过了修饰器的基本使用方式,修饰器的使用方式很多,还可以使用函数嵌套的方式。

用个简单例子来演示一下:

def A(x):	#修饰器函数
    def B():	#修饰器函数里面嵌套的函数
        print("B")

    B()
    return x


@A	#使用修饰器
def C():	#被修饰函数
    print("C")


C()	#调用被修饰函数

运行结果:

B
C

从运行结果来看,被修饰函数里面也是可以进行函数嵌套的。


4.其他使用方式:闭包

闭包的也是属于函数里的一种,只是比较特殊,关于闭包的知识我这里就不复述了,在【Python基础】里面我们有讲过关于闭包的知识,忘了的可以去看看或者百度一下,我们来看看修饰器里面的闭包是怎么使用的。

def A(x):
    def B():
        print("B")
        return x()  # C 无差别调用只能一层,这里是无法通过x调用

    return B


@A
def C():
    print("C")


C()

运行结果:

B
C

可以看出在修饰器函数中,闭包也是可以正常使用的。

在这里插入图片描述


5.其他使用方式:被修饰的函数有参数的形式

如果被修饰的函数有参数传递,参数只能传给修饰器函数里面的内嵌函数。

def A(x):	#修饰器函数
    print(x)

    def B(aa, bbb):  # 内嵌函数,接收被修饰函数传递的参数
        print("B")
        print(aa, bbb)
        return x(aa, bbb)  # C

    return B


@A
def C(n, nn):	#被修饰函数
    print("C")


C("10", "20")

运行结果:

<function C at 0x00000206BED6B670>
B
10 20
C

可以看出来,虽然被修饰函数 C()传递了参数给修饰器函数 A() ,但是默认传递的还是C()这个对象,修饰器函数A()接受的还是被修饰函数C(),参数传到了修饰器函数A()里面的函数B()。

就算你在A()里面加两个形参,它也接受不了,只会报错,前面在修饰器的使用说明我们已经说了,“被修饰的函数作为参数,传递给修饰器”,所以修饰器函数接受的只是被修饰函数这个对象,其他如果要传递参数,那么修饰器函数里面就就得有其他函数来接受传递的参数。


6.其他使用方式:有参数的修饰器,无参数的函数,使用内嵌函数收取参数

如果修饰器有参数但被修饰函数却没有参数的情况下,只能使用内嵌函数来收取参数。

def fun(a=20):
    print(a)

    def c(bb):
        print(bb)
        return bb  # 可以无差别调用,因为是在第二层才接收的funB,相当于第一层

    return c


@fun(30)
def funB():
    print("xixixi")


funB()

运行结果:

30
<function funB at 0x0000025DAE4DD0D0>
xixixi

三、Python内置的修饰器

前面我们所讲的都是我们自定义的修饰器,在Python中是有内置的修饰器,我们来学习一下常见的三种Python内置修饰器:staticmethod,classmethod,property

它们的作用就是把类中的方法变为静态方法,包括类的类属性和类方法,具体的使用,我们来简单的看一下。

1.property

property可以将方法变为属性,被修饰的方法名必须和property下方的方法名一样,property只能用于私有属性

我们来做一个对比,我们在不使用property的情况下,创建一个类并使用类里面的方法,是这样的:

class A:
    def __init__(self):
        self.age = 20   #实例属性
    def getattr(self):  #打印实例属性
        print(self.age)
    def setattr(self,newage):	#给实例属性赋值
        self.age = newage

a = A()
a.setattr(30)
a.getattr()

运行结果:

30

没有什么问题,运行结果正常,那我们用内置修饰器property来试试看有何不同。

在使用内置修饰器property之前,我们得补充一个点:私有属性,在函数内部创建的属性是私有属性,在不经过特殊处理之前,无法在函数外部使用,私有属性前面加两个下划线表示,比如类里面的__name就表示私有属性,我们通过一个简单的例子来看看:

在这里插入图片描述

可以看出来,私有属性在类内部是可以进行访问的,在外部是不行的(做了一些处理之后是可以访问的,这里就不介绍了,可以网上查一下)。

回到我们的内置修饰器property,在前面不使用property的例子中,我们做一下修改,加入内置修饰器property。

class A:
    def __init__(self):
        self.__age = 20

    @property
    def age(self):	#被修饰的方法
        return self.__age

    @age.getter
    def age(self):	#被修饰的方法
        return self.__age

    @age.setter
    def age(self, newage):	#被修饰的方法
        self.__age = newage


a = A()
a.age = 200
print(a.age)

运行结果:

200

从这个例子我们可以看出,内置修饰器property是可以用于私有属性,并且可以将方法变为属性,比如a.age就是在调用属性而不是方法了,在类中虽然多次出现了age()这个方法但却没有因为方法覆盖而报错,也是因为property的存在,property规定了被修饰的方法名必须和property下方的方法名一样。

在这里插入图片描述

2.staticmethod – 静态方法

内置修饰器staticmethod是一个静态方法,功能是将被修饰的方法从类中抽离出来,成为独立的函数,该函数不能访问类的属性

我们先写一个简单的类,通过使用与不使用staticmethod来做一个对比,不使用的情况下

class B:
    def __init__(self, name):
        self.name = name

    def eat(self):  # 打印传递的值
        print(self.name)


b = B("龙叔")
b.eat()

运行结果:

龙叔

这个没有什么问题,再来看看使用了staticmethod,直接加上看看:

在这里插入图片描述

报错了,原因就是加上了@staticmethod 后,eat()这个方法就变成了一个普通的函数,它的位置虽然在类里面,但实际上却相当于一个普通的函数,并不是类的方法了,所以你得给它传递一个值,不然形参self就没有值传递从而报错了。

正确的写法应该是:

在这里插入图片描述

我们来总结一下这个staticmethod 静态方法:

1. 这个函数是一个普通函数,只有这个类能用
2. 静态方法可以设置参数,也可以不需要参数了(self)
3. 该函数不能访问类的属性

3.classmethod

被classmethod修饰的方法,与实例方法的区别是接收的第一个参数不是self,而是cls(当前类的具体类型),被修饰的方法无法访问实例属性,但是可以访问类属性。

class B:
    age = 10

    def __init__(self, name):
        self.name = name

    def sleep(self):    #打印
        print(self)

    @classmethod
    def eat(cls):  # 被修饰的函数
        print(cls)  #看看传递的是类还是值
        print(cls.age)  #访问类的属性
        print(self.name)	#访问实例对象的属性


b = B("龙叔")
b.sleep()
b.eat()

运行结果:

<__main__.B object at 0x0000024FD7B3CFA0>
<class '__main__.B'>
10
Traceback (most recent call last):
  File "D:\pythonProject1\专题2.py", line 21, in <module>
    b.eat()
  File "D:\pythonProject1\专题2.py", line 14, in eat
    print(self.name)
NameError: name 'self' is not defined

通过结果可以看出,从sleep()和eat() 打印的对象来看,sleep()传递的对象是创建的实例对象b,而被修饰器修饰的函数eat()传递的是类;从eat()访问类的属性和实例属性来看,访问类的属性是没有问题的,但访问实例对象的属性时就报错了。

所以验证了前面讲的:被classmethod修饰的方法,与实例方法的区别是接收的第一个参数不是self,而是cls(当前类的具体类型),被修饰的方法无法访问实例属性,但是可以访问类属性。

~

运行结果:

<__main__.B object at 0x0000024FD7B3CFA0>
<class '__main__.B'>
10
Traceback (most recent call last):
  File "D:\pythonProject1\专题2.py", line 21, in <module>
    b.eat()
  File "D:\pythonProject1\专题2.py", line 14, in eat
    print(self.name)
NameError: name 'self' is not defined

通过结果可以看出,从sleep()和eat() 打印的对象来看,sleep()传递的对象是创建的实例对象b,而被修饰器修饰的函数eat()传递的是类;从eat()访问类的属性和实例属性来看,访问类的属性是没有问题的,但访问实例对象的属性时就报错了。

所以验证了前面讲的:被classmethod修饰的方法,与实例方法的区别是接收的第一个参数不是self,而是cls(当前类的具体类型),被修饰的方法无法访问实例属性,但是可以访问类属性。

今天的分享就到这里,欢迎大家在评论区留言交流!
在这里插入图片描述

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

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

相关文章

FPGA/SoC控制机械臂

FPGA/SoC控制机械臂 机器人技术处于工业 4.0、人工智能和边缘革命的前沿。让我们看看如何创建 FPGA 控制的机器人手臂。 介绍 机器人技术与人工智能和机器学习一起处于工业 4.0 和边缘革命的最前沿。 因此&#xff0c;我认为创建一个基础机器人手臂项目会很有趣&#xff0c;我们…

系统架构设计师之使用McCabe方法可以计算程序流程图的环形复杂度

系统架构设计师之使用McCabe方法可以计算程序流程图的环形复杂度

Elasticsearch:使用 Open AI 和 Langchain 的 RAG - Retrieval Augmented Generation (二)

这是继上一篇文章 “Elasticsearch&#xff1a;使用 Open AI 和 Langchain 的 RAG - Retrieval Augmented Generation &#xff08;一&#xff09;” 的续篇。在这篇文章中&#xff0c;我主要来讲述 ElasticVectorSearch 的使用。 我们的设置和之前的那篇文章是一样的&#xff…

【C++基础入门】43.C++中多态的概念和意义

一、函数重写回顾 父类中被重写的函数依然会继承给子类子类中重写的函数将覆盖父类中的函数通过作用域分辨符&#xff08;::&#xff09;可以访问到父类中的函数 二、多态的概念和意义 面向对象中期望的行为 根据实际的对象类型判断如何调用重写函数父类指针&#xff08;引用…

【蓝桥杯】蓝桥杯双周赛第二场ABCD题

A题&#xff1a;新生 知识点&#xff1a;下一届是第几届蓝桥杯…… 新一届蓝桥杯大赛即将在2024年拉开序! 作为大一新生的小蓝&#xff0c;在听说了这场盛大的比赛后&#xff0c;对其充满了期待与热情。但作为初次参赛的新手&#xff0c;他对蓝桥杯的相关赛制和历史并…

LVS负载均衡(LVS简介、三种工作模式、十种调度算法)

LVS简介 LVS&#xff08;Linux Virtual Server&#xff09;是一种基于Linux内核的高可用性负载均衡软件。它通过将客户端请求分发到多个后端真实服务器&#xff0c;提高系统性能和可靠性。LVS支持多种调度算法&#xff0c;如轮询、最少连接、源地址哈希等&#xff0c;用于决定…

番外8.2---配置/管理硬盘

""" Step1&#xff1a;清楚磁盘、硬盘&#xff08;HDD&#xff09;、光驱的概念及是否具有包含关系。 Step2&#xff1a;硬件设备&#xff08;IDE、SCSI、SATA、NVMe、软驱等&#xff09;命名方式及在linux系统里对应的文件名称。 Step3&#xff1a;&#xff1…

保存 uboot图像配置

一. 简介 本文学习如何保存经过图像配置&#xff0c;与加载 自己的配置文件。 之前几篇文章学习了&#xff1a;uboot 经过图形化配置 dns 命令功能。地址如下&#xff1a; uboot通过图像化界面配置 dns命令-CSDN博客 uboot通过图像化界面配置 dns命令验证-CSDN博客 二. 保…

微信管理系统的便捷功能:自动回复

宝子们 你有遇到以下头疼的问题吗&#xff1f; 1、每日手动一遍又一遍点“添加”来通过大量好友? 2、每日总要花至少半个或1个小时来回复刚通过的好友? 3、经常切换聊天窗口复制粘贴同样的内容回复客户&#xff1f; 4、一键转发操作多了被系统提示过于频繁&#xff1f; 5、…

虹科 | 解决方案 | 汽车示波器 学校教学方案

虹科Pico汽车示波器是基于PC的设备&#xff0c;特别适用于大课堂的教学、备课以及与师生的互动交流。老师展现讲解波形数据&#xff0c;让学生直观形象地理解汽车的工作原理 高效备课 课前实测&#xff0c;采集波形数据&#xff0c;轻松截图与标注&#xff0c;制作优美的课件&…

Ubuntu22.04 交叉编译阿里oss c-sdk

一、交叉编译openssl Ubuntu20.04 交叉编译openssl 1.0.1f_编译前去除 makefile 中所有的"-m64"字段_qq76211822的博客-CSDN博客文章浏览阅读319次。Ubuntu20.04 交叉编译openssl_编译前去除 makefile 中所有的"-m64"字段https://blog.csdn.net/sz7621182…

windows下使用FFmpeg开源库进行视频编解码完整步聚

最终解码效果: 1.UI设计 2.在控件属性窗口中输入默认值 3.复制已编译FFmpeg库到工程同级目录下 4.在工程引用FFmpeg库及头文件 5.链接指定FFmpeg库 6.使用FFmpeg库 引用头文件 extern "C" { #include "libswscale/swscale.h" #include "libavdevic…

Spring关于注解的使用

目录 一、使用注解开发的前提 1.1 配置注解扫描路径 二、使用注解创建对象 2.1 Controller&#xff08;控制器储存&#xff09; 2.2 Service&#xff08;服务储存&#xff09; 2.3 Repository&#xff08;仓库储存&#xff09; 2.4 Component&#xff08;组件储存&#xff09; …

自研框架跻身全球 JS 框架榜单,排名紧随 React、Angular 之后!

前言 终于实现了一个重要目标&#xff01;我独立研发的 JavaScript 框架 Strve&#xff0c;最近发布了重大版本 6.0.2。距离上次大版本发布已经接近两个月&#xff0c;期间进行了大量的优化&#xff0c;使得框架性能和稳定性都得到了大幅度的提升。在上次的大版本更新中&#…

DevOps持续集成-Jenkins(2)

文章目录 DevOpsDevOps概述Integrate工具&#xff08;centos7-jenkins主机&#xff09;Integrate概述Jenkins介绍CI/CD介绍Linux下安装最新版本的Jenkins⭐Jenkins入门配置安装必备插件⭐安装插件&#xff08;方式一&#xff1a;可能有时会下载失败&#xff09;安装插件&#x…

负载均衡--Haproxy

haproxy 他也是常用的负载均衡软件 nginx 支持四层转发&#xff0c;七层转发 haproxy也可以四层和七层转发 haproxy&#xff1a;法国人开发的威利塔罗在2000年基于C语言开发的一个开源软件 可以支持一万以上的并发请求 高性能的tcp和http负载均衡2.4 1.5.9 haproxy&#…

微服务-Eureka

文章目录 提供者与消费者Eureka注册中心搭建EurekaServer服务注册服务发现项目结构 提供者与消费者 Eureka注册中心 服务消费者该如何获取服务提供者的地址信息&#xff1f; 服务提供者启动时向eureka注册自己的信息 eureka保存这些信息 消费者根据服务名称向eureka拉取提供者信…

10.Z-Stack协议栈移植

一、下载Z-Stack协议栈源文件 安装过程全部默认下一步即可&#xff0c;安装完成后会在C盘根目录下生成一个【Texas Instruments】文件夹 二、删除一些不必要的文件 将【ZStack-CC2530-2.3.0-1.4.0】文件夹&#xff0c;复制到自己放置ZigBee工程的文件夹下进入到【ZStack-CC253…

vue + html + Lodop打印功能

1.官网下载 http://www.lodop.net/download.html 2.解压安装运行 点击CLodop_Setup_for_Win32NT.exe进行安装 3.vue代码实现&#xff08;具体操作见官网&#xff1a;http://www.lodop.net/faq/pp35.html&#xff09; 3.1把官方提供的LodopFuncs.js文件保存到项目某个目录下 …

C# 使用base64编码用于加密和解密

base64编码原理&#xff1a;Base64编码是一种将二进制数据转换为ASCII字符的编码方式。它使用64个字符来表示二进制数据&#xff0c;包括大小写字母、数字和两个符号。将3个字节的二进制数据转换为4个字符的文本数据&#xff0c;如果不足3个字节&#xff0c;则在末尾补0&#x…