当前位置: 首页 > news >正文

Python 泛型 - 如何在实例方法中获取泛型参数T的类型?

先上解决方法:https://stackoverflow.com/questions/57706180/generict-base-class-how-to-get-type-of-t-from-within-instance

再来简单分析下源码。

talk is cheap, show me the code.

from typing import Dict
Dict[str, int]

Dict只是一个类型,并不是字典类,但是我们可以通过一些方法,拿到其真正意义上的类。

typing 库提供了 get_argsget_origin 函数。

get_args

顾名思义,获取参数。这里的获取参数指的是获取类的泛型参数。

什么意思?

看一看 dict 的代码注解,可以看到 dict 支持泛型的,接受两个泛型参数:_KT_VT
这就是我们可以通过 Dict[str, int] 的方式对字典内键值对的类型进行更加具体标注原因。

所以,Dict[str, int]str, int 就是 Dict 的泛型参数。

通过 get_args 就可以获取到内部的泛型参数,就像这样:


get_origin

获取原始,原始什么?就是获取类型的原始类。

Dict 本身仅仅一个类型,它并不支持去实例化一个字典对象。

如何通过 Dict 而拿到 dict 呢,则就需要使用 get_origin 获取原始类。

就像这样:

值得注意的是:get_args get_origin 仅支持内置的类型

这里再贴一下,官方文档的描述

get_args、get_origin 为泛型类型与特殊类型形式提供了基本的内省功能。
对于 X[Y, Z, ...] 形式的类型对象,这些函数返回 X(Y, Z, ...)。如果 X 是内置对象或 collections class 的泛型别名, 会将其标准化为原始类。如果 X 是包含在其他泛型类型中的联合类型或 Literal(Y, Z, ...) 的顺序会因类型缓存,而与原始参数 [Y, Z, ...] 的顺序不同。对于不支持的对象会相应地返回 None()

在实例方法中获取原始类及泛型参数的数据类型

接下来看另一种情况,我希望在 Demo 类内部,获取 T 它所对应的真实类,例如:

from typing import Generic, TypeVar, get_argsT = TypeVar("T")
class Demo(Generic[T]):def __init__(self):print(get_args(self.__class__))Demo[int]()

看着没什么问题,其实这里会打印空内容。

为什么呢?

self.__class__ 确实获取到了 Demo 类,但是这个 Demo 类并不是最原始的样子。

get_args(self.__class__) 就等同于 get_args(Demo),自然拿不到泛型参数。

我们想要的语句是长这个样子的 get_args(Demo[int]),那么,现在的问题就转换成了如何在 Demo 类内部获取到 Demo[int],我暂且就叫它原始类吧。

先上解决方法,在方法内部调用 self.__orig_class__ 即可获取到原始类。

from typing import Generic, TypeVar, get_args  T = TypeVar("T")  class Demo(Generic[T]):  def __init__(self):  pass  def test(self):c = get_args(self.__orig_class__)[0]assert c is int  demo = Demo[int]()  
demo.test()

在上面这个示例中,当调用 test 方法时,在该方法内部即可知道泛型参数 T 所对应的类型是什么了。

这里有一个疑问, 为什么`get_args(self.__orig_class__)[0]`写在了`test`方法内,而不是`__init__`初始化方法内。先说结论:通过以上方法获取泛型参数的类型,只能在该泛型类初始化完成之后才可以使用,即必须在`__init__, __new__`执行后才可调用。

简要分析 Generic 源码

接下来,让我一个人墨迹一会儿,我会简单分析 Generic 的源码,看一看为什么必须在 __init__, __new__ 之后才可以使用。

再多提一句,对于类本身是没有 __orig_class__ 这个属性的,但是为什么我们又可以使用它。

简单点说就是,__orig_class__ 是后来加上的,最初并没有做初始化,如下图 Pycharm 提示了该类不存在 __orig_class__ 属性。

在分析代码之前,可以再看下 Generic[T] 这个写法,它有这么一个中括号的。这个符号在 Python 就是一个语法糖。我们知道,列表对象可以通过 lst[0] 获取到对应下标的元素,字典对象可以通过 d[key] 获取到对应 key 的值,这都是因为列表类和字典类了实现了 __getitem__ 魔术方法。

对于列表和字典,它们都是已经被实例化的对象,而 Generic 是一个类,所以对于类同样支持 [] 语法糖的魔术方法,叫做 __class_getitem__ ,方法名也是比较好记住的,无非就是加了 __class__ 前缀。

现在我们就跳到 Generic 类里面,找到 __class__getitem__ 方法,为了方便浏览,我在以下代码中写注释了。

# 缓存泛型类
@_tp_cache
def __class_getitem__(cls, params):# params 很好理解,就是我们传入的泛型参数,`Generic[T]`中 T 就是这个 paramsif not isinstance(params, tuple):params = (params,)if not params and cls is not Tuple:raise TypeError(f"Parameter list to {cls.__qualname__}[...] cannot be empty")msg = "Parameters to generic types must be types."# 类型检查params = tuple(_type_check(p, msg) for p in params# 只有 Generic 极其子类才可以使用泛型 TypeVarif cls in (Generic, Protocol):# Generic and Protocol can only be subscripted with unique type variables.# 判断所有泛型参数都是 TypeVar 的实例if not all(isinstance(p, TypeVar) for p in params):raise TypeError(f"Parameters to {cls.__name__}[...] must all be type variables")if len(set(params)) != len(params):raise TypeError(f"Parameters to {cls.__name__}[...] must all be unique")else:# Subscripting a regular Generic subclass._check_generic(cls, params)# 重点return _GenericAlias(cls, params)

我们关注 __class_getitem__ 的返回结果,返回了 _GenericAlias 的实例,接收两个参数:clsparams。这里的 cls 指的是 Generic 或其子类,params 就是泛型参数。

重点来了,对于 class Demo(Generic[T]) 而言,我们并不是继承至 Generic 而是 _GenericAlias()

_GenericAlias 是什么?看看它的初始化方法,如下:

def __init__(self, origin, params, *, inst=True, special=False, name=None):self._inst = instself._special = specialif special and name is None:orig_name = origin.__name__name = _normalize_alias.get(orig_name, orig_name)self._name = nameif not isinstance(params, tuple):params = (params,)# origin 就是 Generic 或继承自它的子类self.__origin__ = originself.__args__ = tuple(... if a is _TypingEllipsis else() if a is _TypingEmpty elsea for a in params)# parmas 转换成了 self.__parameters__self.__parameters__ = _collect_type_vars(params)self.__slots__ = None  # This is not documented.if not name:self.__module__ = origin.__module__

其它参数,我们就不了解了,在 Generic 也只传了两个参数,对应这里面的 originparams

class Demo(Generic[T]):pass

对于上面代码,换一种写法就是:

class Demo(_GenericAlias(Generic, T)):pass

一个类是不是会用到 () 来实例化一个对象,如下:

Demo()

在 Python 中的 () 也是一个语法糖,对应的是 __call__ 方法,所以 Demo() 等同于 Demo.__call__(),本质上就是调用父类的 _GenericAlias(Generic, T).__call__ 方法,所以我们应该去找 _GenericAlias__call__ 方法。

_GenericAlias 没有实现 __call__,而是它继承的父类实现的 _BaseGenericAlias,如下:

def __call__(self, *args, **kwargs):if not self._inst:raise TypeError(f"Type {self._name} cannot be instantiated; "f"use {self.__origin__.__name__}() instead")result = self.__origin__(*args, **kwargs)try:result.__orig_class__ = selfexcept AttributeError:passreturn result

self.__origin__ 就是本例中的 Demo 类,可以看到这里先是进行实例化了,然后再将 self 绑定在了 result 上。注意,这里的 self 指的就是 _GenericAlias 对象。

会到上文讲到的 get_orgs

get_args(self.__orig_class__)[0]

这里获取的 self.__orig_class__ 就是 _GenericAlias 的对象,get_orgs 源码如下:

def get_args(tp):"""Get type arguments with all substitutions performed.For unions, basic simplifications used by Union constructor are performed.Examples::get_args(Dict[str, int]) == (str, int)get_args(int) == ()get_args(Union[int, Union[T, int], str][int]) == (int, str)get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])get_args(Callable[[], T][int]) == ([], int)"""if isinstance(tp, _AnnotatedAlias):return (tp.__origin__,) + tp.__metadata__if isinstance(tp, (_GenericAlias, GenericAlias)):res = tp.__args__  # 访问了 __args__if _should_unflatten_callable_args(tp, res):res = (list(res[:-1]), res[-1])return resif isinstance(tp, types.UnionType):return tp.__args__return ()

显而易见,就是访问了 _GenericAlias__args__ 成员。我们再看下 __args__ 是什么?

    def __init__(self, origin, args, *, inst=True, name=None,_paramspec_tvars=False):super().__init__(origin, inst=inst, name=name)if not isinstance(args, tuple):args = (args,)self.__args__ = tuple(... if a is _TypingEllipsis elsea for a in args)self.__parameters__ = _collect_parameters(args)self._paramspec_tvars = _paramspec_tvarsif not name:self.__module__ = origin.__module__

__args__ 来自参数传递 args ,让我们回到 Generic.__class_getitem__ 方法,如下:

    @_tp_cachedef __class_getitem__(cls, params):if not isinstance(params, tuple):params = (params,)# 中间省略大部分内容,都是为了组装 paramsreturn _GenericAlias(cls, params,_paramspec_tvars=True)

显而易见,就是将 [] 中的泛型参数传了进来,并实例化了 _GenericAlias 对象,并在泛型类实例化时(即调用 __call__ 时)将其绑定在该实例的 __orig_class__ 成员上。

这也解释了为什么只能在非 __init__ 实例方法中访问 __orig_class__,因为泛型类实际上是实例化之后才被绑定的 __orig_class__

相关文章:

Python 泛型 - 如何在实例方法中获取泛型参数T的类型?

先上解决方法:https://stackoverflow.com/questions/57706180/generict-base-class-how-to-get-type-of-t-from-within-instance 再来简单分析下源码。 talk is cheap, show me the code. from typing import Dict Dict[str, int]Dict只是一个类型,并不…...

Shell语法基础总结

Shell 变量使用变量只读变量删除变量变量类型Shell 字符串单引号与双引号字符串获取字符串长度提取子字符串拼接字符串Shell 数组定义数组读取数组获取数组的长度Shell 传递参数Shell 基本运算符算术运算符关系运算符布尔运算符逻辑运算符字符串运算符Shell 信息输出命令Shell …...

架构基本概念和架构本质

什么是架构和架构本质 在软件行业,对于什么是架构,都有很多的争论,每个人都有自己的理解。此君说的架构和彼君理解的架构未必是一回事。因此我们在讨论架构之前,我们先讨论架构的概念定义,概念是人认识这个世界的基础&…...

taobao.trade.ordersku.update( 更新交易的销售属性 )

¥开放平台免费API必须用户授权 只能更新发货前子订单的销售属性 只能更新价格相同的销售属性。对于拍下减库存的交易会同步更新销售属性的库存量。对于旺店的交易,要使用商品扩展信息中的SKU价格来比较。 必须使用sku_id或sku_props中的一个参数来更新&a…...

算法实战应用案例精讲-【图像处理】使用scikit-image做图像处理(最终篇)(附python代码实现)

目录 高级滤波 autolevel bottomhat 与 tophat enhance_contrast entropy equalize gradient 其它滤波器...

数据结构与算法(四):树结构

前面讲到的顺序表、栈和队列都是一对一的线性结构,这节讲一对多的线性结构——树。「一对多」就是指一个元素只能有一个前驱,但可以有多个后继。 一、基本概念 树(tree)是n(n>0)个结点的有穷集。n0时称…...

taobao.trade.shippingaddress.update( 更改交易的收货地址 )

¥开放平台免费API必须用户授权 只能更新一笔交易里面的买家收货地址 只能更新发货前(即买家已付款,等待卖家发货状态)的交易的买家收货地址 更新后的发货地址可以通过taobao.trade.fullinfo.get查到 参数中所说的字节为GBK编码的&…...

VS Code安装及(C/C++)环境配置(Windows系统)

参考资料2份: 从零开始的vscode安装及环境配置教程(C/C)(Windows系统)_光中影zone的博客-CSDN博客_vscode运行配置https://blog.csdn.net/qq_45807140/article/details/112862592 VSCode配置C/C环境 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/87864677 五…...

【Spring Cloud Alibaba】006-OpenFeign

【Spring Cloud Alibaba】006-OpenFeign 文章目录【Spring Cloud Alibaba】006-OpenFeign一、概述1、Java 项目实现接口调用的方法HttpclientOkhttpHttpURLConnectionRestTemplate WebClient2、Feign 概述二、Spring Cloud Alibaba 快速整合 OpenFeign1、添加依赖2、启动类加注…...

挚文集团短期内不适合投资,长期内看好

来源:猛兽财经 作者:猛兽财经 挚文集团(MOMO)在新闻稿中称自己是“中国在线社交和娱乐领域的领军企业”。 该公司旗下的陌陌是中国“陌生人社交网络”移动应用类别的领导者,并在2022年9月拥有超过1亿的月活跃用户。探…...

clion开发的常用快捷键以及gitcrlf的问题

前段报错:git config core.autocrlf false 然后删除app目录下的文件,除了.git文件夹然后 git bash ,执行 git reset --hardclion常用快捷键:Double shift 搜索文件F9调试F9运行到断点Ctrl F8 打断点F7单步步入Shift F8 单步跳出F8执行下一行代…...

LeetCode 格雷编码问题

格雷编码格雷编码的定义格雷编码的码表LeetCode 89. 格雷编码实例思路与代码思路一:找规律代码一代码二思路二:与自然数之间的关系(你必须知道,这个规律要去百度才知道)代码一LeetCode 1238. 循环码排列实例思路与代码…...

java生成html文件输出到指定位置

String fileName "filename.html";StringBuilder sb new StringBuilder();// 使用StringBuilder 构建HTML文件sb.append("<html>\n");sb.append("<head>\n");sb.append("<title>HTML File</title>\n");sb.a…...

华为OD机试用Python实现 -【微服务的集成测试】(2023-Q1 新题)

华为OD机试300题大纲 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:blog.csdn.net/hihell/category_12199275.html 华为OD详细说明:https://dream.blog.csdn.net/article/details/128980730 微服务的集成测试…...

软考高级信息系统项目管理(高项)原创论文——整体管理(2)

<...

js版 力扣 62. 不同路径

一、题目描述 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。问总共有多少条不同的路径&#xff1…...

Qt音视频开发16-通用悬浮按钮工具栏的设计

一、前言 通用悬浮按钮工具栏这个功能经过了好几个版本的迭代&#xff0c;一开始设计的时候是写在视频控件widget窗体中&#xff0c;当时功能简单就放一排按钮在顶部悬浮widget中就好&#xff0c;随着用户需求的变化&#xff0c;用户需要自定义悬浮条的要求越发强烈&#xff0…...

商品比价API使用说明

商品数据分析 国内最早的比价搜索平台&#xff0c;专注于电商大数据的分析&#xff0c;有10年技术和数据沉淀。 公司自主研发的爬虫、搜索引擎、分布式计算等技术&#xff0c; 实现了对海量电商数据的及时监测、清洗和统计。 数据丰富 详细使用api 数据采集维度&#xff…...

基于 TensorFlow 的植物识别教程

首先&#xff0c;需要准备一些训练数据集。这些数据集应该包含两个文件夹&#xff1a;一个用于训练数据&#xff0c;另一个用于测试数据。每个文件夹应该包含子文件夹&#xff0c;每个子文件夹对应一个植物的种类&#xff0c;并包含该植物的图像。接下来&#xff0c;我们需要使…...

渗透测试之主机探测存活性实验

渗透测试之主机探测存活性实验实验目的一、实验原理1.1 TCP/IP协议1. TCP2. IP1.2 Ping的原理二、实验环境2.1 操作机器2.2 实验工具三、实验步骤1. 学会使用ping命令2. 使用Nmap进行多种方式的探测总结实验目的 熟悉TCP/IP协议、Ping命令基本概念学习nmap、SuperScan扫描方式…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…...

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

Python 包管理器 uv 介绍

Python 包管理器 uv 全面介绍 uv 是由 Astral&#xff08;热门工具 Ruff 的开发者&#xff09;推出的下一代高性能 Python 包管理器和构建工具&#xff0c;用 Rust 编写。它旨在解决传统工具&#xff08;如 pip、virtualenv、pip-tools&#xff09;的性能瓶颈&#xff0c;同时…...

CSS设置元素的宽度根据其内容自动调整

width: fit-content 是 CSS 中的一个属性值&#xff0c;用于设置元素的宽度根据其内容自动调整&#xff0c;确保宽度刚好容纳内容而不会超出。 效果对比 默认情况&#xff08;width: auto&#xff09;&#xff1a; 块级元素&#xff08;如 <div>&#xff09;会占满父容器…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

【C++进阶篇】智能指针

C内存管理终极指南&#xff1a;智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...

wpf在image控件上快速显示内存图像

wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像&#xff08;比如分辨率3000*3000的图像&#xff09;的办法&#xff0c;尤其是想把内存中的裸数据&#xff08;只有图像的数据&#xff0c;不包…...