Python 中的 typing.ClassVar 详解

一、ClassVar 的定义和基本用途

ClassVar 是 typing 模块中提供的一种特殊类型,用于在类型注解中标记类变量(静态变量)。根据官方文档,使用 ClassVar[…] 注释的属性表示该属性只在类层面使用,不应在实例上赋值
例如:

from typing import ClassVarclass Starship:stats: ClassVar[dict[str, int]] = {}  # 类变量damage: int = 10                     # 实例变量

上例中,stats 被标注为 ClassVar,表示它是一个共享的类级别变量;damage 则是普通的实例变量。需要注意的是,ClassVar 只是类型提示,不改变运行时行为;它本身不是类,也不能用于 isinstance() 或 issubclass() 检查。

二、ClassVar 与实例变量的区别

在 Python 中,类中定义并赋值的变量默认属于类属性,所有实例共享同一份数据。使用 ClassVar 注解后,静态类型检查器会将该属性视为“类变量”,并禁止通过实例来赋值。相反,实例变量通常在 init 方法中初始化,或者在类体中仅使用类型注解而不赋默认值。例如,下面的写法会导致混淆, 不建议这样写:

class A:x: ClassVar[int] = 1  # 类变量y: int = 2            # 实际上,这里 y 是在类体中赋值,运行时也是类变量def __init__(self):self.y = 2        # 将 y 定义为实例变量

如上所示,不要在类体中给期望的实例变量赋值,否则该变量既被注解为实例属性,又被赋予了类属性的默认值,导致类型检查和逻辑上的混淆。正确做法是:在类体中仅使用注解不赋值(y: int),并在 init 中给实例属性赋值;或者如果需要类级别配置,则显式使用 ClassVar 注解。类型检查器(如 mypy)会识别 ClassVar 注释的属性,并在不当使用时发出警告或错误。

PEP 526 背景: ClassVar 的引入源自 PEP 526(2016 年提出),该 PEP 为变量注解提供了语法。PEP 526 明确指出,通过 ClassVar[…] 注解的变量标识为类变量,不应在实例上被赋值。在 PEP 526 的示例中,有如下类:

class Starship:captain: str = 'Picard'               # 实例属性(默认值)damage: int                           # 实例属性(无默认值)stats: ClassVar[dict[str, int]] = {}  # 类属性

这里 stats 是真正意义上的类变量(比如记录游戏统计数据),而 captain 只是为实例提供了一个默认值。PEP 526 解释说,区分类变量和实例变量对静态类型检查器很有帮助,例如下面代码中,如果不使用 ClassVar:

enterprise = Starship(3000)
enterprise.stats = {}   # 如果 stats 是类变量,这里将被标记为错误
Starship.stats = {}     # 正确,直接修改类的属性

使用 ClassVar 让类型检查器能够在类似 enterprise.stats = {} 这种赋值操作上报错。

三、适用场景

ClassVar 主要用于需要共享或静态存储的类属性场景,例如:

3.1 共享配置或常量

类中定义的配置信息、常量或缓存(如超时、默认值等),需要被所有实例共享。通过 ClassVar 标记后,这些属性被视为类级别常量
例如:

class Config:DEFAULTS: ClassVar[dict] = {'timeout': 5, 'verbose': False}
3.2 dataclass 中排除实例字段

在使用 @dataclass 时,可以用 ClassVar 标记那些不应出现在 init 中的类属性。ClassVar 注释的字段不会被视为实例字段,因此不会成为构造参数。
例如:

@dataclass
class Point:x: inty: intcount: ClassVar[int] = 0  # 类级计数器,不作为实例字段

在上例中,count 不会出现在自动生成的 init 方法参数中。

3.3 类型协议

在使用结构化子类型(PEP 544 的 Protocol)时,可以用 ClassVar 区分类属性和实例属性,帮助类型检查器理解协议的成员性质。RealPython 的示例也指出:“应该使用 ClassVar 来区分类属性和实例属性”。

3.4 其他静态用途

如实现单例模式、缓存计算结果、计数器等场合,ClassVar 都可用于标识那些跨实例共享的数据。

四、代码示例

from typing import ClassVarclass Starship:stats: ClassVar[dict[str, int]] = {}  # 类变量damage: int = 10                      # 实例变量enterprise = Starship()
print(Starship.stats)  # 输出 {}
print(enterprise.stats)  # 同样输出 {}(实例读取的是类属性)
enterprise.stats = {'hits': 1}  # 通过实例赋值:会创建实例属性,不推荐
print(Starship.stats)  # 仍输出 {},说明类属性未被改变
print(enterprise.stats)  # 输出 {'hits': 1},实例属性覆盖了类属性

在上述示例中,stats 被标记为 ClassVar,表明它应作为类属性共享使用。从运行结果可以看到,通过实例 enterprise.stats = {…} 赋值实际上会新建一个实例属性,不影响原有的类属性;类型检查器会将这种通过实例修改 ClassVar 的行为视为错误。

另一个示例演示了 dataclass 中的 ClassVar 用法:

from dataclasses import dataclass
from typing import ClassVar@dataclass
class Counter:x: inty: inttotal: ClassVar[int] = 0  # 类级计数器# 创建实例时,__init__ 只接收 x, y 两个参数,total 不在其中
c1 = Counter(1, 2)
c2 = Counter(3, 4)
print(Counter.total, c1.total, c2.total)  # 输出 0 0 0
Counter.total = 5
print(c1.total, c2.total)  # 输出 5 5(所有实例共享类属性)

在这个例子中,total 使用了 ClassVar 注解,所以在 dataclass 自动生成的构造函数中不会包含它。所有实例都共享同一个 total 值,且修改 Counter.total 会影响所有实例的读值。

五、 ClassVar 与 @classmethod、@staticmethod 的关系

ClassVar、@classmethod 和 @staticmethod 属于不同的概念,它们之间没有直接关联:
ClassVar 用于标记类属性(变量),仅影响类型提示;它不会改变对象的绑定行为。
@classmethod 是一个装饰器,用于定义类方法,使方法第一个参数接收类本身(通常命名为 cls),可用于访问或修改类状态。
@staticmethod 也是装饰器,将方法转为静态方法,不接收类或实例的隐式参数,类似普通函数。
简而言之,ClassVar 关注的是数据(属性)级别的静态标记,而 @classmethod/@staticmethod 是对方法的绑定方式的修饰,两者作用域不同、互不干扰。

六、 常见误用及陷阱

误以为运行时生效: ClassVar 只是类型标记,对程序运行时无任何影响。不要指望它在运行时阻止属性被修改;它不会生成新的行为或存储方式。

在实例上赋值: 尽管运行时允许 instance.var = …,但类型检查器会认为这是错误的。mypy 示例中指出,将类变量通过实例赋值会报错(但代码运行时依然会新建实例属性)。正确的操作应修改类属性:ClassName.var = …。

省略类型参数: 如果在 ClassVar 中省略类型(例如写成 x: ClassVar = 0),这会导致该属性被视为隐式 Any 类型。这一行为可能与预期不符,应始终提供具体类型:ClassVar[int]。

ClassVar 不是类: ClassVar 不能用于 isinstance() 或 issubclass() 等检查;它本身也不是可实例化的类。

类型变量(TypeVar)不可用: ClassVar 的类型参数必须是具体类型,不能使用类型变量。例如 ClassVar[T](其中 T 是 TypeVar)是非法的,会被静态检查器视为错误。

与 Final 一起使用: PEP 591 建议不要同时将 ClassVar 和 Final 注解标记在同一个属性上。Python 3.12 及更早版本中,两者同时使用会导致错误;正确的做法是仅使用 Final 注解即可表示类级常量。在 Python 3.13 及以后版本中,文档已允许 ClassVar 与 Final 嵌套使用。

滥用概念: 不要将 ClassVar 当成 Java/C++ 中那种“静态变量”语义上的特殊对象;在 Python 中,它仅是一个类型提示工具,不会自动创建或隐藏实例属性。

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

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

相关文章

架构与UML4+1视图

简单对比分析 架构41视图 架构41视图是由Philippe Kruchten提出的,用于描述软件系统的架构。它包括以下五个视图: 逻辑视图:描述系统的功能需求,展示系统的静态结构,通常使用类图、对象图等。开发视图:…

Redis 八股

目录 数据类型 字符串: List: HASH: Set: Zset: BitMap:(这个及以下是后来新增的数据结构) HyperLogLog: GEO: Stream: 主要数据结构 …

基于协同过滤的文学推荐系统设计【源码+文档+部署】

基于协同过滤的文学推荐系统设计 摘要 随着信息技术的飞速发展和文学阅读需求的日益多样化,构建一个高效、精准的文学推荐系统变得尤为重要。本文采用Spring Boot框架,结合协同过滤算法,设计并实现了一个基于用户借阅行为和社交论坛互动的文学…

鸿蒙电脑:五年铸剑开新篇,国产操作系统新引擎

出品 | 何玺 排版 | 叶媛 前不久,玺哥发布的《鸿蒙电脑,刺向垄断的利刃,将重塑全球PC市场格局》发布后,获得了读者朋友的积极反馈,不少都期望鸿蒙电脑早日发布。 如今,它真来了! 5月8日&…

EWOMAIL

1、错误 Problem: problem with installed package selinux-policy-targeted-3.14.3-41.el8.noarch package fail2ban-server-1.0.2-3.el8.noarch requires (fail2ban-selinux if selinux-policy-targeted), but none of the providers can be installed - package fail2ban-…

qt5.14.2 opencv调用摄像头显示在label

ui界面添加一个Qlabel名字是默认的label 还有一个button名字是pushButton mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <opencv2/opencv.hpp> // 添加OpenCV头文件 #include <QTimer> // 添加定…

Spring三级缓存的作用与原理详解

在Spring框架中&#xff0c;Bean的创建过程涉及到了三级缓存机制。这个机制主要是为了提高单例模式下bean实例化和依赖注入的效率。本文将深入探讨Spring中的三级缓存&#xff0c;以及其在bean生命周期中的重要作用。 首先&#xff0c;让我们理解什么是三级缓存。Spring中的三…

IoTDB集群的一键启停功能详解

IoTDB&#xff08;Internet of Things Database&#xff09;作为一种专为物联网设计的高性能时序数据库&#xff0c;支持单机与分布式等多种部署模式。随着节点数量的增加&#xff0c;手动管理集群的启动与停止过程变得繁琐。为了提升部署效率&#xff0c;IoTDB 提供了一键启停…

Oracle学习日记--Oracle中使用单个inert语句实现插入多行记录

目录 前言&#xff1a; 问题现象&#xff1a; 问题分析&#xff1a; 解决方法&#xff1a; 1、insert into ... union all句式 2、insert all into ...select 1 from dual句式 总结&#xff1a; 前言&#xff1a; 最近项目中使用到了Oracle数据库&#xff0c;由于Oracle数…

LabVIEW 程序运行时内存不足报错原因

在 LabVIEW 程序开发与运行过程中&#xff0c;内存不足报错并退出是常见且棘手的问题。这不仅影响程序稳定性&#xff0c;还可能导致数据丢失与系统崩溃。以下从程序设计、硬件资源、系统环境等多维度深入剖析其成因&#xff0c;帮助开发者准确定位并解决问题。 ​ 一、程序设…

【GAN网络入门系列】一,手写字MINST图片生成

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 博主简介&#xff1a;努力学习的22级本科生一枚 &#x1f31f;​&#xff1b;探索AI算法&#xff0c;C&#xff0c;go语言的世界&#xff1b;在迷茫中寻找光芒…

Baklib加速企业AI数据智理转型

Baklib智理AI数据资产 在AI技术深度渗透业务场景的背景下&#xff0c;Baklib通过构建企业级知识中台架构&#xff0c;重塑了数据资产的治理范式。该平台采用智能分类引擎与语义分析模型&#xff0c;将分散在邮件、文档、数据库中的非结构化数据转化为标准化的知识单元&#xf…

如何在Windows右键新建菜单中添加自定义项,将notepad添加到新建菜单

一、简介 Windows 右键新建菜单的核心管理机制隐藏在注册表的 HKEY_CLASSES_ROOT 根键中。这里存在两种关键注册表项&#xff1a;文件扩展名项和文件类型项&#xff0c;它们共同构成了新建菜单的完整控制体系。 以常见的.txt文件为例&#xff0c;系统通过以下机制实现新建菜单…

中大型水闸安全监测系统建设实施方案

一、方案背景 随着科技的不断进步&#xff0c;水利工程的数字化转型已经成为提升城市水资源管理效率和增强防洪能力的关键。今天&#xff0c;我们将引导您深入了解我国大中型水闸安全监测管理系统的构建方案&#xff0c;探讨如何运用先进技术确保国家水安全&#xff0c;提升水利…

Gartner《如何有效融合Data Fabric 与Data Mesh数据战略》学习心得

在当今数字化时代,数据已成为企业最为重要的战略资产之一。企业对于高效的数据管理架构的需求日益迫切,以确保能够从海量数据中提取有价值的信息,支持业务决策和创新。近年来,数据编织(Data Fabric)和数据网格(Data Mesh)成为了数据管理领域的两个热门概念,在行业内引…

matlab建立整车模型,求汽车的平顺性

在MATLAB中建立整车模型评估汽车平顺性&#xff0c;通常采用多自由度振动模型。以下是基于四分之一车模型的详细步骤和代码示例&#xff0c;可扩展至整车模型。 1. 四分之一车模型&#xff08;简化版&#xff09; 模型描述 自由度&#xff1a;2个&#xff08;车身垂直位移 z2…

探究电阻分压的带负载能力

我们经常使用两个电阻去分压来获得特定的电压,那么我是两个大阻值电阻分压获得的电压驱动能力强,还是小阻值电阻分压得到的电压驱动能力强呢? 一、电压相同时,电流的大小 下面是两个阻值分压得到的仿真图 电路分析: VCC都是5V,探针1和探针2测到的电压都是1.67V; 根据…

牛客网NC22222:超半的数

牛客网NC22222:超半的数 题目描述 输入输出格式 输入格式&#xff1a; 第一行包含一个整数 n (1 ≤ n ≤ 1000)第二行包含 n 个整数 a_i (1 ≤ a_i ≤ 10^9) 输出格式&#xff1a; 输出一个整数&#xff0c;表示出现次数超过一半的那个数 解题思路 这道题目有多种解法&a…

开发日常中的抓包工具经验谈:Charles 抓包工具与其它选项对比

开发日常中的抓包工具经验谈&#xff1a;HTTPS调试怎么选&#xff1f; 在移动开发或Web API联调时&#xff0c;网络请求常常成为问题定位的第一难题。尤其是面对加密的 HTTPS 请求&#xff0c;传统浏览器调试工具已显得力不从心。 我们团队最近在排查一个安卓应用中的支付延迟…

哈希表实现(1):

1. 哈希&#xff1a; 之前我们的红黑数的查找是由于左边小右边大的原则可以快速的查找&#xff0c;我们这里的哈希表呢&#xff1f; 这里是用过哈希函数把关键字key和存储位置建立一个关联的映射。 直接定址法&#xff08;函数函数定义的其中一种&#xff09;&#xff1a; 直…