【python】类型约束(类型提示的作用)

news2024/10/5 19:16:28

文章目录


前言

  1. python是一种解释型强类型动态语言
  2. python3.5以前是没有类型约束(类型提示)这一功能的
  3. python的类型提示只能起到提示的作用,是为了方便编码和阅读代码,但是仍然程序员可以xjb传,这点挺坑的
  4. 因为python是强类型的动态语言,Python解释器只在程序运行时才会做类型的检查,并且变量的类型在其生命周期内是可以改变的。所以,根本约束不住啊。
    综上,python的这一功能叫做类型提示更为合适,不应叫类型约束;php作为解释型若类型语言的代表,在php7(2016年)都引入了类型约束;python作为最流行的解释性语言,而且还是强类型的,实在不具备可解释性。

一、类型系统

所有的编程语言都有类型系统,类型系统规定了该语言支持的数据类型以及这些数据类型的行为。

1.动态类型

python就是一门典型的动态语言,只在程序运行时才会做类型的检查,并且变量的类型在其生命周期内是可以改变的。 (大白话:不运行我都不知道它是啥类型的,我约束个屁啊,但是php7做到了,作为曾经的phper,我骄傲,但我跑了)

def main():
    if False:
        1 + "Two"  # 1  这里故意写错,整数怎么能和字符串拼接在一起呢,(PHP可以,应该是做了隐士转换,或者重写了+运算符)
    else:
        1 + 2

    stuff = "Hello Eric Idle!"
    print(type(stuff))

    stuff = 2020
    print(type(stuff))


if __name__ == '__main__':
    main()

上述代买的运行结果:

<class ‘str’>
<class ‘int’>

2.静态类型

在静态类型的语言中,如:C、Java,在运行前的编译环节就完成了。

String stuff;
stuff = "Hello";

第一行声明了变量名称为stuff且指定其编译时的类型为String,在该变量的声明周期内,其不可以再被指定为其他数据类型;
第二行中将一个字符串对象赋给了变量stuff,且在其生命周期内,变量的值只可以是字符串对象。

3.鸭子类型

刚学python时,我是理解不了这句话的。
鸭子类型(Duck Typing):如果它走路像只鸭子,呱呱呱的叫声也像一只鸭子,那么它肯定是一只鸭子。
实际上,他讲的是定义数据类型的另一种方式:结构类型,
a str = 1 是名义类型(nominal type,如:str、float、int、list、tuple、dict等)
所以在使用鸭子类型时不关心对象的名义类型,而只关心对象是否实现了某协议所指定的方法。
Python中常见的为支持各类协议所预定义的类及需实现的方法如下表:

协议方法
Containercontains
Hashablehash
Iterableiter
Sizedlen
Callablecall
Sequencegetitemlen
假设我们规定,一种动物如果实现了“鸭子协议”,且该协议中指定了描述鸭子特征的两个方法__waddle__()和__quack__(),那么不管一个动物到底是鹅还是鸡,我们都可以称之为鸭子,这就是鸭子类型的含义。

现在你再看上面这个定义,可能就懂了。

class EricIdle(object):
    def __len__(self):
        return 2020


def main():
    eric = EricIdle()
    print("len(eric) = ", len(eric))


if __name__ == '__main__':
    main()

上述代码的运行结果为:

len(eric) = 2020

由上述代码的运行结果可知,调用len()得到了__len__()的返回值,即要想成功调用len(obj),唯一的限制仅在于obj中定义了__len__()方法。实际上,len()的实现类似于下列代码:

二、变量注解

1.变量注解的语法

在PEP 526中向Python引入了注解变量的语法.

from typing import ClassVar, Dict, List
import sys


primes: List[int] = []

captain: str


class Starship:
    stats: ClassVar[Dict[str, int]] = {}


def main():
    print(sys.modules[__name__].__annotations__)  # 1
    print(Starship.__annotations__)


if __name__ == '__main__':
    main()

上述代码的运行结果为:

{‘primes’: typing.List[int], ‘captain’: <class ‘str’>}
{‘stats’: typing.ClassVar[typing.Dict[str, int]]}
  1. 注解变量的列表和字典都是typing模块中的,其首字母均为大写,且通过方括号指定列表或字典中元素数据类型
  2. 对于被注解的变量,注解信息保存在模块层级的__annotation__字典属性中,这即为# 1处代码的含义。

2.注解鸭子类型

from typing import Sized


def len(obj: Sized) -> int:  # 1
    return obj.__len__()


def main():
    print(len.__annotations__)


if __name__ == '__main__':
    main()


输出:

{‘obj’: <class ‘collections.abc.Sized’>, ‘return’: <class ‘int’>}

对于参数obj,函数len()并不关心其名义类型是什么,只要其实现了__len__方法即可,进一步地,即只要其支持Sized协议即可,故有如上的语法。


三、复杂(复合型)变量的注解

1.引入

其实是符合型变量的注解,比如List[Dict],和自定义的Class
实际上,对于列表、元组等复合类型数据,为其添加类型提示,和为简单类型数据添加类型提示基本一致,如:

import sys
names: list = ["Guido", "Jukka", "Ivan"]
version: tuple = (3, 7, 1)
options: dict = {"centered": False, "capitalize": True}
print(sys.modules[__name__].__annotations__)

上述代码的运行结果为:

{‘names’: <class ‘list’>, ‘version’: <class ‘tuple’>, ‘options’: <class ‘dict’>}

如names[2]是str,options[“centered”]是bool,但是如果变量names中的元素也是通过其他变量来表示,通过上述类型提示就无法获知如names[2]的数据类型。
上面一句话没看懂没关系,我给个例子:

import sys
from typing import Dict, List, Tuple
aaa = "abc"
names: List[str] = ["Guido", "Jukka", "Ivan",aaa]
version: Tuple[int, int, int] = (3, 7, 1)
options: Dict[str, bool] = {"centered": False, "capitalize": True}


print(sys.modules[__name__].__annotations__)

names:List[str] 就在告诉你少废话,你里面都是 str,不管是常量"123",还是变量aaa
上述代码的运行结果为:

{‘names’: typing.List[str], ‘version’: typing.Tuple[int, int, int], ‘options’: typing.Dict[str, bool]}

一个函数可能只期望接收的参数是一个序列(sequence),而并不关心其究竟是list、 str、 tuple,使用typing.Sequence来注解函数的参数。

from typing import List, Sequence

def square(elems: Sequence[float]) -> List[float]:
    return [x**2 for x in elems]

根据Python官方定义,sequence并不特指某一特定的数据类型,而仅是:

An iterable which supports efficient element access using integer indices via the __getitem__() special method and defines a __len__() method that returns the length of the sequence.
一个可迭代对象,该可迭代对象实现魔法方法__getitem__()并定义一个返回序列长度的__len__()方法,可以支持使用整数索引高效的访问其中的元素。
Some built-in sequence types are list, str, tuple, and bytes.
一些內置的序列类型有list、 str、 tuple以及bytes。

2.难题

类型提示发生嵌套时怎么办?

from typing import List, Tuple
List[Tuple[str, str]] # 是什么含义?

List[Tuple[str, str]] # 是什么含义? 他会越嵌套越多
别名的方式解决

from typing import List, Tuple

Card = Tuple[str, str]
Deck = List[Card]


def deal_hands(deck: Deck) -> Tuple[Deck, Deck, Deck, Deck]:
    """模拟4人抓牌的方式将一副牌平均分成4份"""
    return deck[0::4], deck[1::4], deck[2::4], deck[3::4]


def main():
    print(Card)
    print(Deck)
    print(deal_hands.__annotations__)
    

if __name__ == '__main__':
    main()

3. Any的妙用

any类型是我们的“躺平“方案, 实在嵌套太多了,类型提示本来是帮助我们理解代码,但层层嵌套,层层定义后,会伤害我们理解代码,我实在不能描述嵌套的是个啥了。就上any
在typing模块中,有一个名为Any的类型恰好可以用来注解无法确定的任意类型。

import random
from typing import Any, Sequence


def choose(items: Sequence[Any]) -> Any:
    return random.choice(items)


def main():
    print(choose.__annotations__)

    
if __name__ == '__main__':
    main()

上述代码的运行结果为:

{‘items’: typing.Sequence[typing.Any], ‘return’: typing.Any}

在我写这篇文章之前,我不知道这种类型应该返回啥
php上好像有mix:array,exception

def get_num(n:int)->float:
	if n == 0:
	   raise ValueError('n!=0')
	return 100/n

这种代码不会报错,但会给看的人造成困扰,以为任何情况下都返回float。
这个时候就可以用Any了,

4.类型变量

这个可就牛逼大方了,类比实现了JAVA/C++的泛型,使用typing模块中TypeVar类可以实现类似泛型的功能

import random
from typing import Tuple, Sequence, TypeVar


Card = Tuple[str, str]
Chooseable = TypeVar("Chooseable", str, Card)


def choose(items: Sequence[Chooseable]) -> Chooseable:
    return random.choice(items)


def main():
    choose([("♠", "A"), ("♡", "K")])
    choose(["P1", "P2", "P3", "4"])
    choose([1, 2, 3, 4])
    print(choose.__annotations__)


if __name__ == '__main__':
    main()

上述代码运行结果如下:

(‘♠’, ‘A’)
P2
2
{‘items’: typing.Sequence[~Chooseable], ‘return’: ~Chooseable}

虽然我们在程序中通过TypeVar创建了一个自定义类型Chooseable,并指定其可且仅可为str或Card,但实际上程序依然会正常执行,只是在带有类型检查器的IDE(如此处使用的PyCharm)或第三方类型检查器中(如大名鼎鼎的mypy),才会以提示或错误的形式显现出来。

在这里插入图片描述

5.类型Optional

在做web api时,可选项参数(非必填项)建议的类型,在fastapi框架中很常见。

import random
from typing import List, Tuple, Sequence, TypeVar, Optional

Card = Tuple[str, str]
Chooseable = TypeVar("Chooseable", str, Card)


def choose(items: Sequence[Chooseable]) -> Chooseable:
    return random.choice(items)


def player_order(names: List[str], start: Optional[str] = None) -> List[str]:
    """旋转玩家顺序,使得start_player第一个出牌"""
    if start is None:
        start = choose(names)
    start_idx = names.index(start)
    return names[start_idx:] + names[:start_idx]


def main():
    print(player_order.__annotations__)
    

if __name__ == '__main__':
    main()

用来解决这个问题,通常变量start都应该是一个字符串,但是其也可以不传,此事default一个非字符串值None。

参考资料
[1] Python Type Checking (Guide)
[2] PEP 484 – Type Hints

总结

虽然Python中的类型提示是其一个非必须的特性,有和没有这个特性你都可以写出任何代码,但是在你的代码中使用类型提示可以使你的代码更具有可读性、更容易查找隐藏的bug并且使你的代码架构更加清晰

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

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

相关文章

【Python】基于高德地图API的坐标转换函数

【Python】基于高德地图API的坐标转换函数 API申请&#xff1a; lbs.amap.com/api/webservice/guide/api/convert/产品介绍 坐标转换是一类简单的HTTP接口&#xff0c;能够将用户输入的非高德坐标&#xff08;GPS坐标、mapbar坐标、baidu坐标&#xff09;转换成高德坐标。 …

【学习笔记之Linux】权限之目录权限与默认权限

权限概念 一件事是否允许被谁“做”&#xff0c;这就是权限。权限 用户 文件属性。   在Linux上&#xff0c;用户分为普通用户和root。root是超级管理员 ≈ 天王老子&#xff0c;只能够有一个。root的命令提示符是#&#xff1b;普通用户通过root创建&#xff0c;可以有多个…

【Android安全】Protobuf原理与解析

protobuf 简介 抓包时看到header中有这个&#xff1a; content-type: application/x-protobuf说明包的content是以protobuf格式编码的 关于protobuf的介绍&#xff0c;可以参考&#xff1a; https://techkranti.com/what-is-protobuf-explained-for-hackers/ protobuf 的背…

C++——类和对象(一)

目录 一. 面向过程和面向对象 二. 类的引入 三. 类的定义 1.结构 2.类的作用域 3.类的两种定义方式 四. 类的访问限定符及封装 1.引入 2.访问限定符 3.封装 五. 类的实例化 六. 类对象模型 七. this指针 1.this指针的引出 2.this指针的特性 一. …

JavaScript设计模式之面向对象编程

为了深入地学习 javascript &#xff0c;奔着一名标准 Web 开发人员的标准&#xff0c;想要深入了解一下面向对象的编程思想&#xff0c;提高自己模块化开发的能力&#xff0c;编写可维护、高效率、可拓展的代码&#xff0c;最近一直拜读 《JavaScript设计模式》 &#xff0c;对…

【LeetBook】二叉树

参考资料&#xff1a;LeetBook / 二叉树 前言 差不多的解题思路就是dfs能够解决&#xff0c;其次就是bfs。 主要是递归的解法。 一刷就是了解了 解题的 思路 后序再补一些二叉树的题再刷一刷 目录树的介绍树的遍历前序遍历中序遍历后序遍历层序遍历(广度优先搜索)递归解决问题“…

【FPGA】中值滤波处理BMP图片

文章目录一、中值滤波二、BMP图片格式三、功能实现1.代码设计思路2.shift IP核3.代码实现四、结果测试参考博客一、中值滤波 中值滤波法是一种非线性平滑技术&#xff0c;它将每一像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值。 中值滤波是基于排序统计理论…

【GO】K8s 管理系统项目[API部分--Ingress]

K8s 管理系统项目[API部分–Ingress] 1. 接口实现 service/dataselector.go import ("sort""strings""time"appsv1 "k8s.io/api/apps/v1"corev1 "k8s.io/api/core/v1"nwv1 "k8s.io/api/networking/v1" )// Ing…

JavaScript client screen offset scroll

文章目录JavaScript client screen offset scrollclientX和clientY、offsetX和offsetY、screenX和screenY、pageX和pageYclientWidth、offsetWidth、scrollWidthwindow.outerWidth、window.innerWidth、document.documentElement.clientWidthJavaScript client screen offset s…

【开发工具】Gradle的安装 与 配置环境变量

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ Gradle安装配置教程一、安装Gradle二、配置环境…

C++: Essential C++ 读书笔记:面向过程编程:调用函数。

1&#xff1a;传值和传址区别 按值传递&#xff1a; 在调用函数中将原函数的值拷贝一份过去给被调用的函数&#xff0c;在被调用函数中对该值的修改&#xff0c;不会影响原函数的值。值传递&#xff0c;变量赋值&#xff0c;修改变量的值------修改的是新地址&#xff08;复制地…

智能家居加速落地,景联文科技提供数据采集标注服务

“以AI驱动智能家居&#xff0c;智能家庭助手和智能家居安防同向发展的智能物联网是目前主流趋势。高质量的标注数据能够高效训练算法&#xff0c;加速应用落地。景联文科技为相关企业提供、智能语音助手、人脸识别、指纹识别门禁系统、非法闯入检测、扫地机器人智能终端控制等…

临床资料研究中的风险因素评估相关指标

前言 写这篇文章是因为最涉及的医学相关的项目比较多&#xff0c;有些常常遇到的概念容易混淆&#xff0c;在这里着重区分一下。&#xff08;感谢广大学霸的分享&#xff09; 1. Ratio 与Rate 的区别 Ratio&#xff1a;表示相对比&#xff0c;简单理解为一个数值相对于另一个…

【1】高危漏洞利用工具 (2023.1.6更新)-- Apt_t00ls

0x01 工具介绍增加CNVD-2023-00895包括&#xff1a;泛微、蓝凌、用友、万户、致远、通达、中间件、安全设备等多个高位漏洞。泛微: e-cology workrelate_uploadOperation.jsp-RCE (默认写入冰蝎4.0.3aes) e-cology page_uploadOperation.jsp-RCE (暂未找到案例 仅供检测poc) e-…

Vivado综合属性之ASYNC_REG

本文验证了综合属性ASYNC_REG对寄存器位置的影响。 ASYNC_REG用于单bit信号采用双&#xff08;或多&#xff09;触发器实现异步跨时钟域的场合&#xff0c;此时所有用于同步的触发器都要标记ASYNC_REG。标记方式为&#xff1a; (* ASYNC_REG "TRUE" *) reg sync_0…

想在2023 年成为前端 Web 开发人员的分步指南

当我开始成为一名前端开发人员时&#xff0c;这是我希望拥有的路线图我想出了这个路线图&#xff0c;它有助于实现成为全能开发人员的目标。让我们开始吧。谁是前端开发人员&#xff1f;好的&#xff0c;现在谁是后端开发人员&#xff1f;那么如何成为一名前端开发人员呢&#…

Java 集合练习题

SourceURL:file:///home/windstorm/Documents/JAVA/JavaCoursePractise/Java 集合练习题.docx 答案&#xff1a; import java.lang.reflect.Array; import java.security.cert.CollectionCertStoreParameters; import java.util.*; public class Main { public static voi…

promise和async用法及区别(详解)

一、promisepromise的概念Promise 是异步编程的一种解决方案&#xff0c;是一个构造函数&#xff0c;自身有all、reject、resolve方法&#xff0c;原型上有then、catch等方法。特点&#xff1a;对象的状态不受外界影响。Promise对象代表一个异步操作&#xff0c;有三种状态&…

【django】模型类中数据的增删改查操作总结

文章目录一、数据库数据操作二、创建对象三、批量创建对象方法一&#xff1a;for循环迭代方法二&#xff1a;bulk_create()四、更新对象save()默认更新所有的字段指定要更新的字段一次性更新多个对象五、查询对象1、管理器2、QuerySet3、检索全部对象a、要注意&#xff1a;4、过…

01 踏上python之旅

Python是一种跨平台的、开源的、免费的、解释型的高级编程语言。它具有丰富和强大的库&#xff0c;能够把用其他语言制作的各种模块很轻松地连接在一起。所以被称为胶水语言。 python的应用领域&#xff1a; Web开发大数据处理人工智能自动化运维开发云计算爬虫游戏开发 解释…