Python入门教程+项目实战-14.5节-函数装饰器

news2024/9/28 1:18:54

目录

14.5.1 理解函数装饰器

14.5.2 理解闭包函数

14.5.3 使用闭包进行功能扩展

14.5.4 更优雅的做法:装饰器语法糖

14.5.5 知识要点

14.5.6 系统学习python


14.5.1 理解函数装饰器

在进入正题前,先看一段有关"装饰"的词语解释,以下内容引自百度百科:

装饰,《辞源》解释为“装者,藏也,饰者,物既成加以文采也。”指的是对器物表面添加纹饰、色彩以达到美化的目的。

"装饰"本身就包含功能扩展的意思,例如对器物进行着色,即为色彩的扩展,对器物添加纹饰,即为纹饰的扩展。我们平时中的读书、健身、学习,亦何尝不是一种扩展,通过诸多途径来提升我们的内里与外在。

以此来进行类比,则很容易理解编程语言中的装饰器也是用来进行功能上的扩展。在面向对象中,装饰器是一种软件设计模式,可以对已有的对象进行功能上的扩展,而无需改变其结构。

在第十六章的课程中,会对面向对象编程进行详细的讲解。

那么,如何使用装饰器来进行功能上的扩展?我们得先学习Python中的闭包函数。

14.5.2 理解闭包函数

闭包函数,简单地理解,就是函数中定义的一个内部函数,该内部函数可以访问外部函数作用域中的参数,变量。

代码实例:

# __desc__ = 定义一个闭包函数
 
def login():
    """    实现用户的登录功能    """
    # 键名表示用户名,键值表示登录状态,0为未登录,1为登录 
 
    __USER_STATUS = {
        "green": 0,
    }
 
    # check就是一个闭包函数,访问了外部函数中的变量__USER_STATUS 
    def check(user_name="guest"):
        """  用户登录时,对用户的登录态进行检查  """
        if __USER_STATUS.get(user_name, 0) == 0:
           print("username incorrect or not login")
 
return check

代码讲解:

在上文代码中,定义了一个login函数,在login函数内部又定义了一个check函数,用来对用户的登录状态进行检查。login函数即为check函数的外部函数,在check函数内部访问了login函数中的_USER_INFO变量。像check这样的函数,即为闭包函数。

14.5.3 使用闭包进行功能扩展

假设我们在web开发中定义了一个欢迎页面,一开始不需要登录即可进入欢迎页。定义一个welcome函数,用来模拟欢迎页面的输出:

#__desc__ = 定义welcome函数,用来输出页面的欢迎信息
 
def welcome(user_name="guest"):
    print("welcome {}".format(user_name))

随着项目的不断迭代,现在需要用户登录以后,才弹出欢迎页。此时如何为welcome函数进行扩展?有两种扩展方案:

(1) 直接对welcome函数进行修改,在welcome函数内部对用户的登录态进行检查

(2) 使闭包函数与welcome函数共享同样的参数,这样可以在闭包函数中对用户的登录态进行检查,如果用户已登录,再执行welcome函数。

采用第一种方案会改变welcome函数的代码逻辑,采用第二种方案,是将代码的扩展逻辑转移到闭包函数中,现在请读者回顾装饰器模式的定义:对已有的对象进行功能上的扩展,而无需改变其结构。采用的第二种方案正是所谓的装饰器模式,在Python的装饰器模式中,其核心在于利用闭包函数来对被装饰的对象,进行功能的扩展。

现在通过第二种方案,来对用户的登录态进行检查,Python中的函数可以接受任意类型的参数,我们可以直接将welcome函数作为参数传递给login函数。

代码实例:

# __desc__ = 通过闭包来对welcome函数进行扩展
 
def login(func):
    """  实现用户的登录功能  """
    # 键名表示用户名,键值表示登录状态,0为未登录,1为登录 
 
    __USER_STATUS = {
        "green": 0,
    }
 
    def check(user_name="guest"):
        """  用户登录时,对用户的账号和密码进行验证  """
        if __USER_STATUS.get(user_name, 0) == 0:
            print("username incorrect or not login")
        else:
            func(user_name)
    return check
 

def welcome(user_name="guest"):
    print("welcome {}".format(user_name))
 
# 执行login函数,将welcome函数作为参数传递给login函数
decorated_welcome = login(welcome)
 
# login返回的函数即为被装饰过后的welcome函数
# 再执行被装饰过的welcome函数
decorated_welcome("green")

14.5.4 更优雅的做法:装饰器语法糖

在14.5.3节中,我们通过闭包函数对其它函数的功能进行了扩展,但在使用上不够直观和自然:

(1) 需要将被装饰的函数作为参数传递给装饰器

(2) 需要再执行返回的闭包函数

Python中提供了语法糖,在函数头前面加上一行@decortator的修饰符,可以对当前函数进行装饰,decortator表示具体的装饰器名。在上文的代码中,login函数就是一种装饰器,现在使用@符号来对welcome函数进行装饰。

# __desc__ = 使用@符号对welcome函数进行装饰
 
def login(func):
    """    实现用户的登录功能    """
    # 键名表示用户名,键值表示登录状态,0为未登录,1为登录 
 
    __USER_STATUS = {
        "green": 0,
    }
 
    def check(user_name="guest"):
        """  用户登录时,对用户的登录态进行检查 """
        if __USER_STATUS.get(user_name, 0) == 0:
            print("username incorrect or not login")
        else:
            func(user_name)
    return check
 
@login
def welcome(user_name="guest"):
    print("welcome {}".format(user_name))

对welcome函数使用@login进行装饰以后,Python会自动将wecome函数作为参数传递给login函数, 并执行返回的闭包函数,这是Python装饰器的核心逻辑所在。装饰器中的闭包函数参数须与被装饰对象的参数一致,在不确定被装饰对象的参数时,可以使用可变参数:*args, **kwargs。使用可变参数的装饰器结构:

def decorator(func):
    def closure(*args, **kwargs):
        func(*args, **kwargs)
    return closure

使用装饰器语法糖时,装饰器也可以携带参数,通常需要再嵌套一层闭包函数,在装饰器的最外层函数定义装饰器的参数,在第二层传递被装饰的对象:

# __desc__ = 在最外层定义装饰器的参数
 
def decorator(*args, **kwargs):
    # 在第二层传递被装饰的对象f
    def closure_outer(func):
        def closure_inner(*args, **kwargs):
            # 在第三层中执行被装饰的对象
            func(*args, **kwargs)
        return closure_inner
    return closure_outer

现在继续对welcome函数进行扩展,当用户未登录时,跳转到登录页面。

代码实例:

# __desc__ = 继续对welcome函数进行扩展
 
def redirect(url):
    """ 定义redirect函数来模拟页面跳转 """
    print("redirect to {}".format(url))
 
 
def login(url):
    """    实现用户的登录功能    """
    # 键名表示用户名,键值表示登录状态,0为未登录,1为登录 
 
    __USER_STATUS = {
    "green": 0,
    }
 
    def __login(func):
        def check(user_name="guest"):
            """  用户登录时,对用户的登录态进行检查 """
                if __USER_STATUS.get(user_name, 0) == 0:
                    redirect(url)
 
                else:
                    func(user_name)
        return check
    return __login
 
@login("/login/")
def welcome(user_name="guest"):
    print("welcome {}".format(user_name))

在Python中,使用函数对象定义的装饰器,被称为函数装饰器。使用类类型定义的装饰器,被称为类装饰器。本篇内容着重讲了函数装饰器,对于类装饰器,也是一样的原理。

在后面的课程中,薯条老师会详细地讲解Python中的类装饰器。

装饰器的核心是对已有的对象进行功能扩展,而无需改变其结构。我们在实际开发中,可通过装饰器为可执行对象扩展缓存,日志输出等功能。

14.5.5 知识要点

(1) 装饰器也是用来进行功能上的扩展。在面向对象中,装饰器是一种软件设计模式,可以对已有的对象进行功能上的扩展,而无需改变其结构。

(2) 闭包函数,简单地理解,就是函数中定义的一个内部函数,该内部函数可以访问外部函数作用域中的参数,变量。

 

14.5.6 系统学习python

 薯条老师简介:资深技术专家,技术作家,著有《Python零基础入门指南》,《Java零基础入门指南》等技术教程。薯条老师的博客:http://www.chipscoco.com, 系统学习后端,爬虫,数据分析,机器学习、量化投资。

 

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

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

相关文章

设计模式第20讲——备忘录模式(Memento)

目录 一、什么是备忘录模式 二、角色组成 三、优缺点 四、应用场景 五、代码实现 5.0 UML类图 5.1 EditorMemento——备忘录(Memento) 5.2 Editor——源发器(Originator) 5.3 History——管理者(Caretaker&a…

java常见算法篇【完整版】

14-常见算法 基本查找/顺序查找 从0索引依次向后查找 二分查找/折半查找 前提条件:数组中的数据必须是有序的核心逻辑:每次排除一半的查找范围 package io.xiaoduo.arithmetic;public class Test1 {public static void main(String[] args) {int[…

2023年10个最佳商业WordPress主题(经过测试和专家挑选)

正在寻找最好的商业WordPress主题? 为了帮助您找到适合您业务的完美主题,我们浏览并测试了15个最受欢迎的商业WordPress主题……然后将列表缩减为10个,为您提供最好的。 您需要制作一个成功的商业网站,以激发对潜在客户的信任并…

哪个平板电脑触控笔比较好用?便宜好用的触控笔测评

对于现在的iPad用户来说,苹果原装的Pencil绝对是最好的电容笔选择。但因为价格太高,很多人都买不起。所以,在实际应用中,如何选择一个具有高性能高性价比的电容笔就显得尤为重要。本人是一名“苹果粉”,又是一个老资格…

力扣方法总结-----动态规划

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、动态规划二、动态规划五部曲1.确定DP数组的含义2.确定递推公式3.确定初始值4.确定遍历顺序5.举例推导DP数组,打印程序中DP数组日志 三、例子 一、动…

Day 58-59 Naive Bayes算法

代码: package dl;import java.io.FileReader; import java.util.Arrays; import java.util.Random;import weka.core.*;/*** The Naive Bayes algorithm.*/public class NaiveBayes {/*** An inner class to store parameters.*/private class GaussianParameters…

el-tree 展开指定层级 节点

示例&#xff1a;只展开一级节点 代码实现&#xff1a; element UI 文档 html代码 defaultExpandedArr 是重点 需要加node-key <el-tree:props"defaultProps":data"treeData":default-expanded-keys"defaultExpandedArr"node-key"id&q…

第十六章Java多线程常见模式

文章目录 同步模式之保护性暂停带超时版 GuardedObjectjoin 原理多任务版 GuardedObject 异步模式之生产者/消费者模式标准库中的阻塞队列阻塞队列的实现加锁实现 生产者消费者模型的作用是什么 同步模式之保护性暂停 定义 即 Guarded Suspension&#xff0c;用在一个线程等待…

LeetCode_链表_中等_445.两数相加 II

目录 1.题目2.思路3.代码实现&#xff08;Java&#xff09; 1.题目 给你两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数字都不会以零开头。 …

C#异常总结

C#异常总结 定义Try语句异常类创建用户自定义异常搜索调用栈的示例异常抛出 定义 程序中的运行时错误&#xff0c;它违反一个系统约束或应用程序约束&#xff0c;或出现了在正常操作时未预料的情形。 Try语句 指明被异常保护的代码块&#xff0c;并提供代码以处理异常。try由…

ICC2:fixed和locked有什么不同?

如题&#xff0c;physical status中locked与fixed有很多小伙伴会搞混&#xff0c;从实用性的角度来讲这两个并没有什么区别&#xff0c;一是工具都不会更改object这两种属性&#xff0c;二是工具都不会在优化过程中移除这两个属性的object。 所以&#xff0c;唯一的区别在于lo…

【JUC-4】线程池实战应用

线程池 线程池创建方式 Executors创建线程池(不推荐) JDK提供的工具类Execurtors创建线程池(不推荐), 列举几个Executors中创建线程池的方法; 查看Executors的源代码发现, 它创建线程池也是通过 new ThreadPoolExecutor() 来创建线程池的. 当然其中有一些特殊的线程池也不是…

Moka AI产品后观察:HR SaaS迈进AGI时代

在AI这条路上&#xff0c;Moka已经走了很远。如今的Moka Eva是在此前AI模型基础上的更进一步。未来AGI时代&#xff0c;HR SaaS会有更多可能性。 出品|产业家 在AI潮水里&#xff0c;Moka正在加速快跑。 在6月28日的2023夏季新品发布会上&#xff0c;国内首个AI原生HR Saa…

流量卡收货地就是归属地,这是什么意思呢?

我们在网上申请流量卡时&#xff0c;会比较关注流量卡归属地这一问题&#xff0c;据小编了解&#xff0c;目前网上的流量卡归属地有两种模式&#xff0c;接下来&#xff0c;小编一一为大家介绍一下&#xff0c;大家可以根据自己的情况来选择。 ​ 在中国的手机号码都有固定的区…

【MySQL】备份数据(导出数据 / 导入数据)

&#x1f3af;导出数据 1、使用 SELECT ... INTO OUTFILE 语句导出数据 SELECT...INTO OUTFILE 是 MySQL 用于导出数据的语句&#xff0c;它允许将查询结果保存到指定的文件中。 该语句的基本语法如下&#xff1a; SELECT column1, column2, ... INTO OUTFILE file_path …

Kotlin~Template模版方法模式

概念 定义算法骨架、代码模版 角色介绍 Abstract ClassConcrete Class UML 代码实现 abstract class Game {protected abstract fun initialize()protected abstract fun startPlay()protected abstract fun endPlay()// 模版fun play(){initialize()startPlay()endPlay()…

Spring面试题--Spring中事务失效的场景有哪些

Spring中事务失效的场景有哪些&#xff1f; 异常捕获处理 Transactional public void update(Integer from, Integer to, Double money) {try {//转账的用户不能为空Account fromAccount accountDao.selectById(from);//判断用户的钱是否够转账if (fromAccount.getMoney() - …

idea运行项目时右下角一直提示Lombok requires enabled annotation processing

出现这个错误是因为使用了Lombok插件的原因&#xff0c;可能是安装时候没有配置好 Lombok requires enabled annotation processing&#xff1a;翻译过来就是Lombok 需要启用注释处理 解决方案 File -> Settings ->Build,Execution,Deployment -> Compiler ->An…

35岁程序员现状,太真实!

“未来每年&#xff0c;我们将会为社会输送1000名工作10年以上的人才。” 这是之前马云在演讲中提到的关于阿里巴巴这样的大厂老员工的问题。总的来讲就是——“毕业”。 也经常能够看到在各个平台有程序员讲到自己35岁的焦虑。 之前&#xff0c;在某平台上就有一个有意思的…

Redis主从复制模式3

谋权篡位 假设在一个Redis集群中&#xff0c;有一台主机和两台从机构成一个Redis集群。此时因外部原因&#xff0c;导致主机宕机&#xff0c;俗话说 “国不可⼀一日无君&#xff0c;军不可一日无帅”&#xff0c;那么需要从剩余的两台从机中再次选出一台主机&#xff0c;从而来…