64、Python之函数高级:装饰器实战,动态语言也能有类型检查

news2024/11/15 12:24:43

引言

Python作为一门动态类型语言,有时候,一个不小心的类型错误只有在实际运行中才有可能被发现。相较而言,静态类型语音虽然不够灵活,但是,类型错误等语法检查在编译期间就可以提前发现了。

那么我们有没有方法在Python代码中也添加类型检查的功能呢?本文就通过装饰器简单实现函数参数类型检查的功能,从而在实战中,进一步加深对装饰器的理解。

本文的主要内容有:

1、Python中的类型注解

2、函数签名获取

3、装饰器实现类型检查

Python中的类型注解

Python是动态类型语言,定义一个变量时,不需要显式指定其类型,但是,这并不意味着不进行类型检查,虽然更多的是运行时进行必要的检查(是否有对应属性、方法的检查)。

在静态类型语言中,定义一个变靓一定要指定变量的类型,比如C语言:

e9b2d01704e1a4e4b2f702e96d9b77db.jpeg

尝试对整型变量a赋值一个浮点数3.14,编译时就会报错了。

动态类型语言,很多时候都是没有编译环节的,所以,类型的错误直到运行时才能被发现。

此外,由于Python中不需要(也不能)显式指定变量的类型,很多时候,对模块的使用方其实是有一定的使用成本的。

Python3.5引入了一个特性:类型注解(Type Annotations),主要用于静态类型检查。

类型注解允许在函数定义中标注参数和返回值的类型,从而使得代码更加清晰、可读。

以实际代码,简单看下类型注解的基本语法:

a70c0b52e8dad89b9fe7e9020054189f.jpeg

1、函数定义中的每个形参后面追加: 类型,来显式提醒形参的数据类型。

2、形参列表之后,以 -> 类型,来提醒函数返回值的数据类型。

3、通过函数对象的属性/元数据:__annotations__可以获取到函数定义中的类型注解信息。

执行结果:

4b4b2caa2d929d3c366eddf953765e6f.jpeg

如果有复杂的类型,可以通过typing模块导入,比如:

642e116f94ccf9320fb742fca49aa213.jpeg

需要说明的是,类型注解只起到了提醒的作用,IDE中也能做到类型不兼容的提醒。但是,即使不遵循类型注解的要求,代码还是可以正常执行的。

所以,虽然不存在语法上的强制要求或者类型检查,类型注解的使用场景,在于当涉及到团队协作,公共模块的开发时,可以通过类型注解以及docstring等增加代码的可读性。

函数签名获取

在我们尝试对Python中的函数调用进行类型检查之前,还有一个前置条件需要解决,就是,我们如何获取到检查函数类型所需要的相关元数据信息,主要是函数的签名信息。

简单介绍一下函数签名的概念。

所谓的函数签名(Function Signature)是指描述了函数的名称、参数(包括参数类型和默认值)以及返回值类型的完整信息。函数签名是关于函数如何调用的完整描述。有助于开发者更好地理解函数的使用方式,并在代码维护和文档编写中能起到很重要的作用。

在Python中如何获取函数的签名信息呢,我们可以尝试之前在简单介绍过的inspect模块。

需要说明的是,本文主要聚焦于通过inspect模块获取函数的签名信息,关于inspect模块更多的内容,后面可以以专题文章来介绍。

直接看代码:

import inspect


# 为了更清楚的查看参数签名,让函数定义更加负责(奇怪)
def add(a: int, b: int = 10, *, c: int = 20) -> int:
    return a + b + c


if __name__ == '__main__':
    sig = inspect.signature(add)
    print(f"函数签名对象的类型:{type(sig)}")
    print(f"函数签名:{sig}")
    # 通过签名对象的parameters属性获取参数信息
    for name, param in sig.parameters.items():
        print(f"参数名:{name},参数信息:{param}")
        print(f"\t 参数类型:{param.kind}")
        print(f"\t 参数默认值:{param.default}")
        print(f"\t 参数注解:{param.annotation}")
    # 也可以获取函数的返回值注解
    print(f"函数返回值注解:{sig.return_annotation}")

执行结果:

3c90673cc8917f8bc081116b58105145.jpeg

其实,很多时候,函数定义中的类型注解,可能是不写的,所以,我们要做函数类型检查时,也不能依赖函数的类型注解。

我们之所以通过inspect模块获取到函数的签名,更多的是想要获取到函数的参数列表(有序的),基于这个参数列表,再结合带参数的装饰器,就可以实现函数调用的类型检查了。

装饰器实现类型检查

要想实现函数类型的检查,我们还需要实现将函数的参数列表与函数调用要求的类型进行一一映射绑定的操作,这一点可以通过函数签名对象的bind()或者bind_partial()函数,首先看下这两个函数的定义:

c25bf4465176d57a41eb7c07d7195403.jpeg

关于_bind()函数的定义,可以自行查看,这里就不展开了。

简单描述一下,进行类型检查的实现步骤:

1、定义一个带参数的装饰器,通过参数,可以动态指定参数调用的数据类型,允许只有部分参数需要进行类型检查。

2、在装饰器函数中,通过inspect模块获取要装饰的原始函数的函数签名。

3、通过函数签名对象的bind_partial()函数,将装饰器的参数与函数签名对象的参数列表进行绑定,形成一一映射关系。

4、在装饰器的内部嵌套函数中(也就是装饰器最终返回的函数对象),将函数的实际调用参数与函数的签名对象的参数列表也进行绑定。

5、将实际调用参数的类型,与前面获取的类型绑定 ,进行一一比较。

概括来说,就是以函数签名为媒介,将函数参数的类型要求,与实际调用传递的函数参数的值进行一一对应,然后进行类型的检查操作。

直接看代码示例:

import inspect


def type_check(*type_args, **type_kwargs):
    def decorator(func):
        sig = inspect.signature(func)
        arg_types = sig.bind_partial(*type_args, **type_kwargs).arguments

        def wrap(*args, **kwargs):
            call_args = sig.bind(*args, **kwargs).arguments
            for name, parma in call_args.items():
                arg_type = arg_types.get(name)
                if arg_type:
                    if not isinstance(parma, arg_type):
                        raise TypeError(f'参数{name}必须是{arg_type}类型')
            return func(*args, **kwargs)

        return wrap

    return decorator


@type_check(int, int)
def add(a, b):
    return a + b


# 也可以只对形参a进行类型检查
@type_check(a=int)
def multiply(a, b):
    return a * b


if __name__ == '__main__':
    print(add(10, 20))
    print(multiply(10, 10.5))
    print('=' * 20)
    print(add(10, 20.5))
    # multiply(10.5, 10)

执行结果:

9f1b587711e057f543f242fd436fc0e9.jpeg

当然,参考类似的思路,对函数参数的取值范围,也是可以通过inspect结合装饰器来动态添加检查机制的,这里就不再列举了,感兴趣的同学可以自行尝试。

总结

本文分别介绍了Python中类型注解的特性、函数签名的概念,以及通过inspect获取函数签名信息、通过bind()、bind_partial()实现对函数签名进行参数的绑定。最终通过一个完整的类型检查的实现代码,展示了通过装饰器实现动态语言的动态类型检查的实现。

感谢您的拨冗阅读,希望对您有所帮助。

09f383b802af2c00b78a4df4e3f53a19.jpeg

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

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

相关文章

Visual Studio Code 月刊 (2024-08)

文章目录 配置文件编辑器Django 单元测试支持vscode.dev 上的 IntelliSenseNotebook 差异查看器通过键盘调整列的大小源代码管理图GitHub Copilot结语 2024 年 8 月 Visual Studio Code(简称 vscode)发布了 version 1.93。该版本带来了许多更新&#xff…

Spring Boot Admin集成与自定义监控告警

目录 一.Spring Boot Admin集成 1.引入依赖 2.添加配置 3.监控界面 二.Spring Boot Admin告警机制 1. 基本告警机制 2. 配置告警 2.1 triggers触发器讲解 3. 自定义通知 3.1 Instance 对象 三.Spring Boot Admin支持的监控属性 1.常见的Spring Boot Admin监控属性 …

进阶SpringBoot之配置 Swagger API 框架信息

Swagger:API 框架 RestFul API 文档在线自动生成工具,API 文档与 API 定义同步更新 Swagger 官网 Maven 仓库 创建 Spring Boot 项目,依赖勾选 Spring Web pom.xml 导入相关依赖: springfox-swagger2、springfox-swagger-ui …

何为数据中台

数据中台 什么是数据中台 2014年马云正式提出“DT(Data Technology)”的概念,人类从IT时代走向了DT时代,阿里内部的数据平台事业部大刀阔斧的建立整个集团的数据资产,同年,阿里从芬兰Supercell公司接触到…

Canny算子 一张图看懂

对于最高值和最低值的设置, 1,high t最大值一般以一阶导数幅度图的最大值的30%-40%来定 2,最小值一般halcon里默认为 low theigh t/3得到 3,canny的优势是有极大值抑制,所以提取的边缘是1个像素的窄边缘。 3&#xff0…

Golang path/filepath包详解:高效路径操作与实战案例

Golang path/filepath包详解:高效路径操作与实战案例 引言基础用法Abs 函数Base 函数Clean 函数Dir 函数Ext 函数FromSlash 和 ToSlash 函数 基础用法Abs 函数Base 函数Clean 函数Dir 函数Ext 函数FromSlash 和 ToSlash 函数 路径操作Join 函数Split 函数Rel 函数Ma…

LabVIEW编程语言出于什么原因开发的?

LabVIEW最初由美国国家仪器公司(NI)于1986年开发,目的是为工程师和科学家提供一种图形化编程环境,简化数据采集、仪器控制、自动化测试和测量系统开发等工作。开发LabVIEW的主要原因包括以下几点: 简化复杂系统开发&am…

《数字图像处理(面向新工科的电工电子信息基础课程系列教材)》Chapter 1课件2024

每一轮备课都有新的感悟。 禹晶、肖创柏、廖庆敏《数字图像处理(面向新工科的电工电子信息基础课程系列教材)》 禹晶、肖创柏、廖庆敏《数字图像处理》资源二维码

Tektronix泰克MSO5204B混合信号示波器4+16通道2G

Tektronix泰克MSO5204B混合信号示波器416通道2G 2 GHz、416 通道、10/5 GS/s(2/4 通道)混合信号示波器,50 M/25 M 记录长度泰克 MSO5204B 2 GHz、416 通道、10/5 GS/s(2/4 通道)混合信号示波器,50 M/25 M …

机器学习强化学习

版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl1. 强化学习概述 1.1 定义与核心概念 强化学习是一种目标导向的机器学习方法,它使智能体能够在环境中通过试错学习最优行为策略。这种学习过程涉及到智能体与环境之间的交互,智能体根据当前状态…

AI基础 L8 Local Search I 局部搜索

Iterative Improvement Algorithms • In many optimization problems, the path to a goal is irrelevant — the goal state itself is the solution • State space a set of goal states — find one that satisfies constraints (e.g., no two classes at same time) —…

《系统安全架构设计及其应用》写作框架,软考高级系统架构设计师

论文真题 随着社会信息化进程的加快,计算机及网络已经被各行各业广泛应用,信息安全问题也变得愈来愈重要。它具有机密性、完整性、可用性、可控性和不可抵赖性等特征。信息系统的安全保障是以风险和策略为基础,在信息系统的整个生命周期中提…

【审批流】基于JAVA开发的工作流审批系统(直接集成或者直接可使用)

基于Javavue开发的智能审批系统,低代码平台 软件资料清单列表部分文档清单:工作安排任务书,可行性分析报告,立项申请审批表,产品需求规格说明书,需求调研计划,用户需求调查单,用户需…

Android APK插件化:DynamicAPK技术如何改变游戏规则

在移动应用开发领域,尤其是Android平台,应用的体积和更新速度一直是开发者和用户关注的焦点。随着应用功能的不断增加,APK文件的大小也在逐渐膨胀,这不仅增加了用户的下载成本,也影响了应用的更新效率。DynamicAPK技术…

数学建模笔记——层次分析法

数学建模笔记——层次分析法 数学建模笔记——层次分析法1. 层次分析法的基本原理和步骤2. 层次分析法的建模过程2.1 问题的提出2.2 模型原理2.3 为该问题建立层次结构模型2.4 构造判断矩阵1. 判断矩阵的含义2. 为该问题构造判断矩阵 2.5 一致性检验1. 一致性检验方法2. 对上述…

【Linux】HTTP协议中的cookie和session

一、B站的登录和未登录——一种登录场景的演示 我们现在上的是B站大学,所以对于B站,我们是很熟悉的。当我们打开浏览器,并访问B站网页时(很熟悉),会发现我们会自动登录上B站,为什么呢&#xff1…

解锁 macOS 剪贴板历史记录,高效复制、粘贴技巧

在Mac上,我们经常需要在不同文档之间复制和粘贴内容。然而,macOS自带的剪贴板只能保存最后一个复制项,这大大限制了我们的工作效率。幸运的是,一些第三方应用程序可以帮助我们查看和管理剪贴板的历史记录,从而提升我们…

基于RP2350 MCU的树莓派Pico 2开发板及MicroPython编程使用

2021年1月21日,树莓派基金会同时发布了第1代RP2040 MCU芯片和基于RP2040 MCU的第1代树莓派Pico开发板(Raspberry Pi Pico/ Raspberry Pi Pico 1)。2024年8月8日,树莓派基金会又发布了第2代RP2350 MCU芯片并推出了基于RP2350 MCU的第2代树莓派Pico开发板(Raspberry Pi Pico 2)…

pandas:一个强大的数据处理Python库

我是东哥,一个热衷于探索Python世界的自媒体人。今天,我要为大家介绍一个在Python数据分析领域中非常强大的库——Pandas。如果你对数据分析充满好奇,或者正在寻找一个简单易用的库来处理和分析数据,那么Pandas绝对是你的不二之选…

MySQL——库操作

首先先来说一下MySQL中常见的操作: 1. 清屏 system clear; 2. 如果你使用的是腾讯云的Ubuntu,登陆的时候用户名可能是ubuntu,进入后可以使用 sudo -i 切换为高级用户 一、创建数据库 create database db_name; 示例: mysql> …