Python 中 in 操作符在集合与列表中的不同行为解析

python 中 in 操作符在集合与列表中的不同行为解析

本文深入探讨了 Python 中 in 操作符在列表和集合这两种数据结构中的不同行为。通过分析其内部实现机制,揭示了为何在某些情况下,使用列表会引发错误,而使用集合却能正常运行。此外,本文还提供了一个自定义类示例,用于更直观地理解 in 操作符的工作原理,并针对 PyTorch 张量比较问题,提出了相应的解决方案。

在 Python 中,in 操作符用于检查某个元素是否存在于一个集合(collection)中。然而,其行为会根据集合的类型(如列表、元组、集合、字典等)而有所不同。理解这些差异对于编写高效且无错的代码至关重要。

in 操作符的工作原理

x in collection 的行为取决于 collection 的类型。

1. 非哈希表集合(列表、元组等)

对于列表和元组等不使用哈希表的集合,in 操作符会遍历集合中的每个元素,并逐一进行比较。其内部逻辑类似于以下伪代码:

def is_in(x, collection):  for c in collection:      if (x is c or x == c):          return True  return False

首先比较 x 和 c 的身份(使用 is),如果身份相同,则返回 True。否则,比较它们的值是否相等(使用 ==)。这个过程会持续到找到第一个匹配项或遍历完整个集合。

立即学习“Python免费学习笔记(深入)”;

2. 哈希表集合(集合、字典等)

对于集合和字典等使用哈希表的集合,in 操作符的查找过程更加高效。其内部逻辑类似于以下伪代码:

def is_in(x, collection):  # 选择集合中哈希值与 x 相同的元素子集  subset = get_subset_by_hash(collection, hash(x))  for c in subset:      if (x is c or x == c):          return True  return False

首先,根据 x 的哈希值,从集合中选择一个子集,该子集包含所有哈希值与 x 相同的元素。然后,遍历这个子集,并进行身份和相等性比较。由于哈希表能够快速定位到可能的匹配项,因此查找速度通常比列表快得多。

注意: 集合中元素的哈希值是在元素添加到集合时计算的,而 x 的哈希值是在使用 in 操作符时计算的。

实例演示

为了更直观地理解 in 操作符的工作原理,我们可以创建一个自定义类 MyObj,并自定义其哈希计算逻辑和相等性比较逻辑:

class MyObj:    def __init__(self, val, hashval):        self._val = val        self._hashval = hashval    def __hash__(self):        print(f"{str(self)} calling __hash__")        return self._hashval    def __eq__(self, other):        print(f"{str(self)} calling __eq__, {other=}")        return super().__eq__(other)    def __repr__(self):        return f""

然后,创建一些 MyObj 实例,并分别添加到集合和列表中:

a = MyObj("a", 123)b = MyObj("b", 456)d = MyObj("d", 456)  # 与 b 具有相同的哈希值print("Creating set `s`")s = set([a, b, d])print("Creating list `lst`")lst = [a, b, d]

通过观察输出,我们可以看到:

在创建集合时,Python 会计算每个元素的哈希值。如果存在哈希冲突(例如,b 和 d 具有相同的哈希值),Python 还会调用 __eq__ 方法进行相等性比较。

接下来,我们可以使用 in 操作符来检查元素是否存在于集合和列表中:

>>> s{, , }>>> b in s calling __hash__True>>> d in s calling __hash__ calling __eq__, other= calling __eq__, other=True
>>> lst[, , ]>>> a in lstTrue>>> b in lst calling __eq__, other= calling __eq__, other=True>>> d in lst calling __eq__, other= calling __eq__, other= calling __eq__, other= calling __eq__, other=True

通过观察输出,我们可以看到:

对于集合,Python 首先计算 x 的哈希值,然后查找哈希值相同的元素。如果存在哈希冲突,还会调用 __eq__ 方法进行相等性比较。对于列表,Python 逐个比较元素,直到找到匹配项或遍历完整个列表。

PyTorch 张量比较问题

在 PyTorch 中,如果尝试比较大小不同的张量,会引发 RuntimeError。这是因为 PyTorch 会检查张量的形状是否兼容。

import torcha = torch.Tensor(2,3)b = torch.Tensor(2)# case 1a:# b  in list([a,a,b]) # raises an error: # RuntimeError: The size of tensor a (2) must match the size of tensor b (3) at non-singleton dimension 0# case 1bb in set([a,a,b]) # True (i.e. no error)

当使用 in 操作符在列表中查找张量时,会按照顺序比较每个张量。如果遇到大小不同的张量,就会引发 RuntimeError。

而当使用 in 操作符在集合中查找张量时,由于集合使用哈希表,因此会首先比较哈希值。在 PyTorch 中,张量的哈希值实际上是其内存地址(id(self))。因此,只有当 b 与集合中某个张量的内存地址相同时,才会进行相等性比较。由于 a 和 b 的内存地址不同,因此不会进行相等性比较,从而避免了 RuntimeError。

解决方案

为了解决这个问题,可以使用 torch.Tensor.size 属性,该属性是一个元组,表示张量的形状。可以创建一个字典,将不同形状的张量分别存储在不同的集合或列表中:

tensor_dict = {}for tensor in [a, b]:    size = tuple(tensor.size())    if size not in tensor_dict:        tensor_dict[size] = set()  # 或 list()    tensor_dict[size].add(tensor)# 检查 b 是否存在于具有相同形状的张量集合中size_b = tuple(b.size())if size_b in tensor_dict and b in tensor_dict[size_b]:    print("b is in the collection")else:    print("b is not in the collection")

这种方法可以避免比较大小不同的张量,从而防止 RuntimeError。

总结

in 操作符在 Python 中的行为取决于集合的类型。理解其内部实现机制对于编写高效且无错的代码至关重要。对于不使用哈希表的集合(如列表和元组),in 操作符会逐个比较元素。对于使用哈希表的集合(如集合和字典),in 操作符会首先比较哈希值,然后再进行相等性比较。在处理 PyTorch 张量时,需要注意大小不同的张量比较可能引发 RuntimeError。可以使用 torch.Tensor.size 属性来避免这种问题。

以上就是Python 中 in 操作符在集合与列表中的不同行为解析的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1373705.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月14日 13:29:54
下一篇 2025年12月14日 13:30:06

相关推荐

  • Go语言字符串分割与多变量赋值教程

    本教程探讨Go语言中如何将字符串分割后的部分赋值给多个变量。与Python等语言不同,Go的strings.Split函数返回一个字符串切片,不能直接进行多变量赋值。文章将详细介绍通过切片索引进行分步赋值的方法,以及针对特定场景(如网络地址)使用net.SplitHostPort等更高效、更安全的解…

    2025年12月16日
    000
  • Go应用内存分析:理解pprof与top报告差异

    Go应用程序在运行时,其pprof堆内存分析报告中的“Total MB”可能远小于top命令显示的“RES”内存。这主要是因为Go运行时为了优化未来分配性能,会缓存已垃圾回收但尚未返还给操作系统的内存。pprof主要反映活跃的Go对象所占用的内存,而top则显示进程从操作系统获取的全部物理内存。现代…

    2025年12月16日
    000
  • Golang如何处理HTTP请求并发控制

    Go语言通过Goroutine和Channel实现HTTP并发控制,常用方法包括:1. 使用带缓冲Channel作为信号量限制并发数;2. sync.WaitGroup协调批量子服务调用;3. rate包实现限流中间件防过载;4. 合理配置Server超时与资源参数。 Go语言处理HTTP请求并发控…

    2025年12月16日
    000
  • Go语言服务器性能测试中的系统瓶颈分析与诊断

    在对go语言编写的简单web服务器进行负载测试时,若观察到随着测试时长增加或连续测试,请求处理速率显著下降,这往往并非go应用本身的性能问题,而更可能是由于测试环境或操作系统层面的资源限制所致。本文将深入探讨此类性能瓶颈的常见原因,并提供诊断与初步优化的方法,帮助开发者识别并解决系统层面的性能障碍。…

    2025年12月16日
    000
  • Go语言中打印uint64常量:解决fmt.Printf溢出错误

    在Go语言中,直接使用fmt.Printf打印像math.MaxUint64这样的大型无类型整数常量时,可能会遇到“constant overflows int”的编译错误。这是因为Go编译器在缺乏明确上下文时,会将无类型整数常量默认推断为int类型,而int无法容纳math.MaxUint64的值…

    2025年12月16日
    000
  • Go语言切片容量收缩:原理、实践与优化考量

    go语言切片在进行截取操作时,其底层数组的容量并不会自动收缩。本文将深入探讨go切片容量管理的机制,介绍如何通过显式复制的方式实现切片容量的有效收缩,并阐明为何go不提供c语言`realloc`式的原地收缩。同时,文章还将提供实践代码,并讨论何时需要进行容量收缩,以及更重要的性能优化策略。 Go切片…

    2025年12月16日
    000
  • Go语言中通过通道高效传输压缩字节流

    本文探讨了在Go语言中如何高效地将压缩后的字节数据通过通道进行传输。针对直接使用chan byte的低效性及zlib.NewWriter的输出处理难题,我们提出了一种优雅的解决方案:将Go通道封装为io.Writer接口。通过定义一个实现了io.Writer接口的通道类型,我们可以让zlib.New…

    2025年12月16日
    000
  • Go程序在htop中显示多个OS进程的深入解析与排查

    本文旨在深入探讨Go程序在操作系统层面,特别是在`htop`工具中,可能出现多个“进程”的现象。我们将解析Go运行时与OS线程的关系,区分`htop`中轻量级进程(LWP)与传统OS进程的概念,并提供针对`go run`命令可能导致的问题及正确的程序终止与部署实践,以帮助开发者准确理解Go程序的行为…

    2025年12月16日
    000
  • Go CGO静态链接C库:解决Go 1.0版本兼容性与正确LDFLAGS配置

    本文深入探讨了go语言通过cgo机制静态链接c库的实践方法。文章指出,在go 1.0版本中,cgo在处理静态库链接时存在一个特定问题,导致即使提供了正确的`.a`文件路径,链接器也可能失败。解决方案在于升级到go 1.1及更高版本,并确保在`cgo_ldflags`中直接指定静态库的绝对路径,而非使…

    2025年12月16日
    000
  • Go语言结构体方法:正确修改成员变量的关键——指针接收器

    本文深入探讨Go语言中结构体方法接收器的重要性,解释了为何值接收器无法修改原始结构体实例的成员变量。通过对比值接收器和指针接收器,揭示了使用指针接收器是实现结构体内部状态持久化修改的关键,并提供了代码示例进行演示,帮助开发者理解并正确选择接收器类型。 理解Go语言中的方法接收器 在go语言中,我们可…

    2025年12月16日
    000
  • Go程序进程行为解析:htop与OS进程的误区与GOMAXPROCS

    Go程序在htop中显示多个“进程”是常见误解,实为轻量级进程或线程。本文深入探讨Go运行时与操作系统进程、线程的关系,区分htop、ps/top的显示差异,并提供观察Go程序进程行为的最佳实践,强调GOMAXPROCS的作用及避免go run可能带来的混淆,旨在帮助开发者准确理解Go应用的底层运行…

    2025年12月16日
    000
  • Golang使用reflect.Type和reflect.Value示例

    答案:Go语言通过reflect包实现运行时反射,可获取变量的类型(Type)和值(Value)。使用TypeOf获取类型名称和种类,ValueOf获取值信息并判断是否可修改。修改值需传入指针并通过Elem解引用,遍历结构体可访问字段名、类型、标签及值,适用于序列化等通用场景,但应避免过度使用以保证…

    2025年12月16日
    000
  • Go Cgo静态链接C库:从Go 1.0的困境到Go 1.1+的解决方案

    在Go语言中,通过Cgo机制与C代码进行交互是其强大功能之一。然而,当涉及到静态链接C库时,开发者可能会遇到一些特定的挑战,尤其是在Go的早期版本中。本文将详细阐述如何在Go中使用Cgo静态链接C库,并提供实际操作步骤和关键注意事项。 1. Cgo静态链接C库的基础 Cgo允许Go程序调用C函数,并…

    2025年12月16日
    000
  • Go语言中switch与if-else的效率深度解析

    go语言的`switc++h`语句相比c/c++更为灵活,可处理布尔表达式,常用于替代冗长的`if-else`链。其效率优势,尤其是在编译器生成跳转表方面,主要限于`case`表达式为整型常量的情况。对于涉及布尔表达式或非整型常量的`case`,`switch`的性能通常与`if-else`相当,编…

    2025年12月16日
    000
  • 跨平台TCP数据传输的序列化策略与性能优化

    在go服务器与ios应用之间进行tcp数据传输时,选择高效的跨平台序列化格式至关重要,尤其是在追求速度的场景下。本文将探讨几种主流的序列化方案,包括json和messagepack,分析其优缺点,并提供选型建议,帮助开发者构建高性能、兼容性强的通信机制。 跨平台数据序列化的挑战 在异构系统(如Go服…

    2025年12月16日
    000
  • 如何使用 Go 解析 JSON 文件到结构体

    本文介绍了如何使用 Go 语言将 JSON 文件解析到结构体中。重点在于结构体字段的导出,以及如何使用 `json` 标签将 JSON 字段映射到结构体字段。通过示例代码,读者可以了解如何正确定义结构体,并使用 `json.NewDecoder` 或 `json.Unmarshal` 函数进行 JS…

    2025年12月16日
    000
  • Go Cgo静态链接C库:从Go 1.0到1.1的演进与实践

    本文深入探讨了go语言中使用cgo静态链接c库的方法与挑战。重点阐述了go版本兼容性(go 1.0与go 1.1+的行为差异)、正确的`#cgo ldflags`语法,以及如何通过`cgo_enabled=0`构建完全静态的go二进制文件(适用于不含cgo依赖的场景),旨在提供清晰的cgo静态链接实…

    2025年12月16日
    000
  • Go语言中读取文件前N个字节及字节数组的正确解读

    本教程详细介绍了如何在go语言中高效读取文件的指定字节数,特别是文件头部信息。文章通过实例代码演示了文件打开、字节读取的关键步骤,并深入探讨了`[]byte`类型在不同输出格式下的解读方法,帮助开发者避免常见的输出误解,确保数据处理的准确性。 1. Go语言中读取文件指定字节 在Go语言中,读取文件…

    2025年12月16日
    000
  • 如何使用Golang实现文件哈希校验

    使用Go语言实现文件哈希校验需通过crypto包中的SHA256等算法,结合os.Open和io.Copy流式读取文件,生成哈希值以验证完整性;示例代码展示了如何计算并比较两个文件的SHA256值,从而高效判断内容一致性。 在Go语言中实现文件哈希校验,主要是通过读取文件内容并使用标准库中的哈希算法…

    2025年12月16日
    000
  • Go语言中字符串分割与多变量赋值实践

    Go语言中,strings.Split函数返回一个字符串切片,不像Python那样能直接一次性赋值给多个变量。本文将详细介绍两种主要方法来处理字符串分割并赋值:一是通过索引分步赋值,适用于通用场景;二是在特定场景下利用net.SplitHostPort等专用函数实现更简洁的直接赋值,并强调了使用时的…

    2025年12月16日
    000

发表回复

登录后才能评论
关注微信