【Python】魔法方法是真的魔法! (第二期)

还不清楚魔术方法?
可以看看本系列开篇:【Python】小子!是魔术方法!-CSDN博客

  • 【Python】魔法方法是真的魔法! (第一期)-CSDN博客

在 Python 中,如何自定义数据结构的比较逻辑?除了等于和不等于,其他rich comparison操作符如何实现以及是否对称?

在 Python 中,如果想自定义数据结构(如自定义日期类)的比较逻辑,可以通过魔术方法实现。例如:

  • 通过定义__eq__函数来改变默认的"等于"比较行为
  • 不等于运算符可以通过定义__ne__函数来自定义逻辑
  • 对于大于、小于等操作符需要定义__gt____lt__等方法
class MyDate:def __init__(self, year, month, day):self.year = yearself.month = monthself.day = daydef __eq__(self, other):if not isinstance(other, MyDate):return NotImplemented # 或者 False,取决于你的需求return (self.year, self.month, self.day) == (other.year, other.month, other.day)def __ne__(self, other):# 通常不需要定义 __ne__,Python 会自动取 __eq__ 的反# 但如果需要特殊逻辑,可以像下面这样定义if not isinstance(other, MyDate):return NotImplementedreturn not self.__eq__(other)def __lt__(self, other):if not isinstance(other, MyDate):return NotImplementedreturn (self.year, self.month, self.day) < (other.year, other.month, other.day)def __le__(self, other):if not isinstance(other, MyDate):return NotImplementedreturn (self.year, self.month, self.day) <= (other.year, other.month, other.day)def __gt__(self, other):if not isinstance(other, MyDate):return NotImplementedreturn (self.year, self.month, self.day) > (other.year, other.month, other.day)def __ge__(self, other):if not isinstance(other, MyDate):return NotImplementedreturn (self.year, self.month, self.day) >= (other.year, other.month, other.day)# 示例
date1 = MyDate(2023, 10, 26)
date2 = MyDate(2023, 10, 26)
date3 = MyDate(2023, 11, 1)print(f"date1 == date2: {date1 == date2}") # True
print(f"date1 != date3: {date1 != date3}") # True
print(f"date1 < date3: {date1 < date3}")   # True
print(f"date3 > date1: {date3 > date1}")   # True

注意:

  1. 通常只需定义__eq__,因为__ne__默认会取__eq__的反结果
  2. rich comparison操作符在没有自定义时会抛出错误
  3. 比较不同类对象时,会优先调用子类的方法

在实现rich comparison时,如果两个对象不是同一类,会如何处理?

当进行rich comparison时:

  • 如果 Y 是 X 的子类,优先使用 Y 的比较方法
  • 否则优先使用 X 的比较方法
    这意味着不同类对象的比较可能触发不同的比较逻辑
class Fruit:def __init__(self, name, weight):self.name = nameself.weight = weightdef __eq__(self, other):print("Fruit __eq__ called")if not isinstance(other, Fruit):return NotImplementedreturn self.name == other.name and self.weight == other.weightdef __lt__(self, other):print("Fruit __lt__ called")if not isinstance(other, Fruit):return NotImplementedreturn self.weight < other.weightclass Apple(Fruit):def __init__(self, name, weight, color):super().__init__(name, weight)self.color = colordef __eq__(self, other):print("Apple __eq__ called")if not isinstance(other, Apple):# 如果对方不是Apple,但可能是Fruit,可以委托给父类if isinstance(other, Fruit):return super().__eq__(other) # 或者自定义不同的逻辑return NotImplementedreturn super().__eq__(other) and self.color == other.colordef __lt__(self, other):print("Apple __lt__ called")if not isinstance(other, Apple):if isinstance(other, Fruit): # 与Fruit比较权重return self.weight < other.weightreturn NotImplementedreturn self.weight < other.weight # 假设苹果之间也按重量比较apple1 = Apple("Fuji", 150, "red")
apple2 = Apple("Gala", 150, "reddish-yellow")
fruit1 = Fruit("Orange", 170)print(f"apple1 == apple2: {apple1 == apple2}") # Apple __eq__ called (比较 apple1 和 apple2)# Fruit __eq__ called (Apple的__eq__调用了super().__eq__)# 输出: apple1 == apple2: False (因为颜色不同)print(f"apple1 == fruit1: {apple1 == fruit1}") # Apple __eq__ called (apple1是Apple类,优先调用其__eq__)# Fruit __eq__ called (Apple的__eq__中调用super().__eq__)# 输出: apple1 == fruit1: Falseprint(f"fruit1 == apple1: {fruit1 == apple1}") # Fruit __eq__ called (fruit1是Fruit类,优先调用其__eq__)# 输出: fruit1 == apple1: Falseprint(f"apple1 < fruit1: {apple1 < fruit1}") # Apple __lt__ called (apple1是Apple类,优先调用其__lt__)# 输出: apple1 < fruit1: True (150 < 170)print(f"fruit1 < apple1: {fruit1 < apple1}") # Fruit __lt__ called (fruit1是Fruit类,优先调用其__lt__)# 输出: fruit1 < apple1: False (170 < 150 is False)

在 Python 中,如何获取自定义数据结构的hash值?

  • 通过调用hash(x)获取默认hash
  • 自定义对象常用作字典、集合的键
  • 注意:Python 不会自动推断rich comparison运算关系
class Point:def __init__(self, x, y):self.x = xself.y = y# 未定义 __eq__ 和 __hash__
p1 = Point(1, 2)
p2 = Point(1, 2)print(f"Hash of p1 (default): {hash(p1)}")
print(f"Hash of p2 (default): {hash(p2)}")
print(f"p1 == p2 (default): {p1 == p2}") # False, 因为默认比较的是对象ID# 将对象放入字典或集合
point_set = {p1}
point_set.add(p2)
print(f"Set of points (default hash): {point_set}") # 包含两个不同的 Point 对象point_dict = {p1: "Point 1"}
point_dict[p2] = "Point 2" # p2 被视为新键
print(f"Dictionary of points (default hash): {point_dict}")

在 Python 中,为什么两个相同的自定义对象在字典中会被视为不同的键?

原因:

  1. 自定义__eq__方法后,默认__hash__会被删除
  2. 需要同时自定义__hash__方法
  3. 必须保证相等对象具有相同hash
class Coordinate:def __init__(self, lat, lon):self.lat = latself.lon = londef __eq__(self, other):if not isinstance(other, Coordinate):return NotImplementedreturn self.lat == other.lat and self.lon == other.lon# 只定义了 __eq__,没有定义 __hash__
coord1 = Coordinate(10.0, 20.0)
coord2 = Coordinate(10.0, 20.0)print(f"coord1 == coord2: {coord1 == coord2}") # Truetry:# 尝试将对象用作字典的键或放入集合coordinates_set = {coord1}print(coordinates_set)
except TypeError as e:print(f"Error when adding to set: {e}") # unhashable type: 'Coordinate'# 定义 __hash__
class ProperCoordinate:def __init__(self, lat, lon):self.lat = latself.lon = londef __eq__(self, other):if not isinstance(other, ProperCoordinate):return NotImplementedreturn self.lat == other.lat and self.lon == other.londef __hash__(self):# 一个好的实践是使用元组来组合属性的哈希值return hash((self.lat, self.lon))p_coord1 = ProperCoordinate(10.0, 20.0)
p_coord2 = ProperCoordinate(10.0, 20.0)
p_coord3 = ProperCoordinate(30.0, 40.0)print(f"p_coord1 == p_coord2: {p_coord1 == p_coord2}")       # True
print(f"hash(p_coord1): {hash(p_coord1)}")
print(f"hash(p_coord2): {hash(p_coord2)}")
print(f"hash(p_coord3): {hash(p_coord3)}")coordinates_map = {p_coord1: "Location A"}
coordinates_map[p_coord2] = "Location B" # p_coord2 会覆盖 p_coord1,因为它们相等且哈希值相同
coordinates_map[p_coord3] = "Location C"print(f"Coordinates map: {coordinates_map}")
# 输出: Coordinates map: {<__main__.ProperCoordinate object at ...>: 'Location B', <__main__.ProperCoordinate object at ...>: 'Location C'}
# 注意:输出的对象内存地址可能不同,但键是根据哈希值和相等性判断的

如何自定义一个合法且高效的hash函数?

要求:

  1. 必须返回整数
  2. 相等对象必须返回相同hash
  3. 推荐做法:
    def __hash__(self):return hash((self.attr1, self.attr2))
    
    避免直接返回常数,否则会导致大量哈希冲突
class Book:def __init__(self, title, author, isbn):self.title = titleself.author = authorself.isbn = isbn # 假设 ISBN 是唯一的标识符def __eq__(self, other):if not isinstance(other, Book):return NotImplemented# 通常,如果有一个唯一的ID(如ISBN),仅基于它进行比较就足够了# 但为了演示,我们比较所有属性return (self.title, self.author, self.isbn) == \(other.title, other.author, other.isbn)def __hash__(self):# 好的做法:基于不可变且用于 __eq__ 比较的属性来计算哈希值# 如果 ISBN 是唯一的,且 __eq__ 主要依赖 ISBN,那么可以:# return hash(self.isbn)# 或者,如果所有属性都重要:print(f"Calculating hash for Book: {self.title}")return hash((self.title, self.author, self.isbn))class BadHashBook(Book):def __hash__(self):# 不好的做法:返回常数,会导致大量哈希冲突print(f"Calculating BAD hash for Book: {self.title}")return 1book1 = Book("The Hitchhiker's Guide", "Douglas Adams", "0345391802")
book2 = Book("The Hitchhiker's Guide", "Douglas Adams", "0345391802") # 相同的书
book3 = Book("The Restaurant at the End of the Universe", "Douglas Adams", "0345391810")print(f"book1 == book2: {book1 == book2}") # True
print(f"hash(book1): {hash(book1)}")
print(f"hash(book2): {hash(book2)}") # 应该与 hash(book1) 相同
print(f"hash(book3): {hash(book3)}") # 应该与 hash(book1) 不同book_set = {book1, book2, book3}
print(f"Book set (good hash): {len(book_set)} books") # 应该是 2 本书bad_book1 = BadHashBook("Book A", "Author X", "111")
bad_book2 = BadHashBook("Book B", "Author Y", "222") # 不同的书,但哈希值相同
bad_book3 = BadHashBook("Book C", "Author Z", "333") # 不同的书,但哈希值相同print(f"hash(bad_book1): {hash(bad_book1)}")
print(f"hash(bad_book2): {hash(bad_book2)}")
print(f"hash(bad_book3): {hash(bad_book3)}")# 由于哈希冲突,字典/集合的性能会下降
# 尽管它们仍然能正确工作(因为 __eq__ 会被用来解决冲突)
bad_book_set = {bad_book1, bad_book2, bad_book3}
print(f"Bad book set (bad hash): {len(bad_book_set)} books") # 应该是 3 本书,但查找效率低
# 当插入 bad_book2 时,它的哈希值是 1,与 bad_book1 冲突。
# Python 会接着调用 __eq__ 来区分它们。因为它们不相等,所以 bad_book2 会被添加。
# 对 bad_book3 同理。

如果自定义对象是mutable的,为什么不应该将其用作字典的key

原因:

  • 字典基于hash值快速访问
  • 对象修改后hash值可能改变
  • 会导致字典检索失效或出错
class MutableKey:def __init__(self, value_list):# 使用列表,这是一个可变类型self.value_list = value_listdef __hash__(self):# 注意:如果列表内容改变,哈希值也会改变# 这使得它不适合做字典的键# 为了能 hash,我们将列表转换为元组return hash(tuple(self.value_list))def __eq__(self, other):if not isinstance(other, MutableKey):return NotImplementedreturn self.value_list == other.value_listdef __repr__(self):return f"MutableKey({self.value_list})"key1 = MutableKey([1, 2])
my_dict = {key1: "Initial Value"}print(f"Dictionary before modification: {my_dict}")
print(f"Value for key1: {my_dict.get(key1)}") # "Initial Value"# 现在修改 key1 内部的可变状态
key1.value_list.append(3)
print(f"Key1 after modification: {key1}") # MutableKey([1, 2, 3])# 尝试用修改后的 key1 (现在是 [1, 2, 3]) 访问字典
# 它的哈希值已经变了
try:print(f"Value for modified key1: {my_dict[key1]}")
except KeyError:print("KeyError: Modified key1 not found in dictionary.")# 尝试用原始状态 ([1, 2]) 的新对象访问
original_key_representation = MutableKey([1, 2])
print(f"Value for original_key_representation: {my_dict.get(original_key_representation)}")
# 输出可能是 None 或 KeyError,因为原始 key1 在字典中的哈希槽是根据 [1,2] 计算的,
# 但 key1 对象本身已经被修改,其 __hash__ 现在会基于 [1,2,3] 计算。
# 字典的内部结构可能已经不一致。# 更糟糕的是,如果哈希值没有改变,但 __eq__ 的结果改变了,也会出问题。# 正确的做法是使用不可变对象作为键,或者确保可变对象在作为键期间不被修改。
# 例如,Python 的内置 list 类型是 unhashable 的:
try:unhashable_dict = {[1,2,3]: "test"}
except TypeError as e:print(f"Error with list as key: {e}") # unhashable type: 'list'

自定义对象在条件判断语句中如何被处理?

默认行为:

  • 自定义对象在布尔上下文中被视为True

自定义方法:

  • 重载__bool__魔术方法
  • 或重载__len__方法(返回 0 时为False

示例:

class MyCollection:def __init__(self, items=None):self._items = list(items) if items is not None else []self.is_active = True # 一个自定义的布尔状态# __bool__ 优先于 __len__def __bool__(self):print("__bool__ called")return self.is_active and len(self._items) > 0 # 例如,只有激活且非空时为 Truedef __len__(self):print("__len__ called")return len(self._items)# 示例 1: __bool__ 定义了逻辑
collection1 = MyCollection([1, 2, 3])
collection1.is_active = True
if collection1:print("Collection1 is True") # __bool__ called, Collection1 is True
else:print("Collection1 is False")collection2 = MyCollection() # 空集合
collection2.is_active = True
if collection2:print("Collection2 is True")
else:print("Collection2 is False") # __bool__ called, Collection2 is False (因为长度为0)collection3 = MyCollection([1])
collection3.is_active = False # 非激活状态
if collection3:print("Collection3 is True")
else:print("Collection3 is False") # __bool__ called, Collection3 is False (因为 is_active 是 False)class MySizedObject:def __init__(self, size):self.size = size# 没有 __bool__,但有 __len__def __len__(self):print("__len__ called")return self.size# 示例 2: 只有 __len__
sized_obj_non_zero = MySizedObject(5)
if sized_obj_non_zero:print("Sized object (non-zero len) is True") # __len__ called, Sized object (non-zero len) is True
else:print("Sized object (non-zero len) is False")sized_obj_zero = MySizedObject(0)
if sized_obj_zero:print("Sized object (zero len) is True")
else:print("Sized object (zero len) is False") # __len__ called, Sized object (zero len) is False# 示例 3: 既没有 __bool__ 也没有 __len__ (默认行为)
class EmptyShell:passshell = EmptyShell()
if shell:print("EmptyShell object is True by default") # EmptyShell object is True by default
else:print("EmptyShell object is False by default")# def __bool__(self):
#     return self.is_valid # 这是笔记中原有的示例,已整合到 MyCollection 中

注意:__bool__优先于__len__被调用

第三期

插眼待更

关于作者

  • CSDN 大三小白新手菜鸟咸鱼长期更新强烈建议不要关注

作者的其他文章

Python

  • 【Python】装饰器在装什么-CSDN博客
  • 【Python】【面试凉经】Fastapi为什么Fast-CSDN博客
  • 【Python】小子!是魔术方法!-CSDN博客
  • 【Python】一直搞不懂迭代器是个啥。。-CSDN博客

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

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

相关文章

Qt 强大的窗口停靠浮动

1、左边&#xff1a; 示例代码&#xff1a; CDockManager::setConfigFlags(CDockManager::DefaultOpaqueConfig); CDockManager::setConfigFlag(CDockManager::FocusHighlighting, true); dockManager new CDockManager(this); // Disabling the Internal Style S…

Linux进程异常退出排查指南

在 Linux 中&#xff0c;如果进程无法正常终止&#xff08;如 kill 命令无效&#xff09;或异常退出&#xff0c;可以按照以下步骤排查和解决&#xff1a; 1. 常规终止进程 尝试普通终止&#xff08;SIGTERM&#xff09; kill PID # 发送 SIGTERM 信号&#xff08;…

使用tensorRT10部署低光照补偿模型

1.低光照补偿模型的简单介绍 作者介绍一种Zero-Reference Deep Curve Estimation (Zero-DCE)的方法用于在没有参考图像的情况下增强低光照图像的效果。 具体来说&#xff0c;它将低光照图像增强问题转化为通过深度网络进行图像特定曲线估计的任务。训练了一个轻量级的深度网络…

SLAM定位常用地图对比示例

序号 地图类型 概述 1 格栅地图 将现实环境栅格化,每一个栅格用 0 和 1 分别表示空闲和占据状态,初始化为未知状态 0.5 2 特征地图 以点、线、面等几何特征来描绘周围环境,将采集的信息进行筛选和提取得到关键几何特征 3 拓扑地图 将重要部分抽象为地图,使用简单的图形表示…

【图像生成1】Latent Diffusion Models 论文学习笔记

一、背景 本文主要记录一下使用 LDMs 之前&#xff0c;学习 LDMs 的过程。 二、论文解读 Paper&#xff1a;[2112.10752] High-Resolution Image Synthesis with Latent Diffusion Models 1. 总体描述 LDMs 将传统 DMs 在高维图像像素空间&#xff08;Pixel Space&#x…

通信安全堡垒:profinet转ethernet ip主网关提升冶炼安全与连接

作为钢铁冶炼生产线的安全检查员&#xff0c;我在此提交关于使用profinet转ethernetip网关前后对生产线连接及安全影响的检查报告。 使用profinet转ethernetip网关前的情况&#xff1a; 在未使用profinet转ethernetip网关之前&#xff0c;我们的EtherNet/IP测温仪和流量计与PR…

TIFS2024 | CRFA | 基于关键区域特征攻击提升对抗样本迁移性

Improving Transferability of Adversarial Samples via Critical Region-Oriented Feature-Level Attack 摘要-Abstract引言-Introduction相关工作-Related Work提出的方法-Proposed Method问题分析-Problem Analysis扰动注意力感知加权-Perturbation Attention-Aware Weighti…

day 20 奇异值SVD分解

一、什么是奇异值 二、核心思想&#xff1a; 三、奇异值的主要应用 1、降维&#xff1a; 2、数据压缩&#xff1a; 原理&#xff1a;图像可以表示为一个矩阵&#xff0c;矩阵的元素对应图像的像素值。对这个图像矩阵进行 SVD 分解后&#xff0c;小的奇异值对图像的主要结构贡…

符合Python风格的对象(对象表示形式)

对象表示形式 每门面向对象的语言至少都有一种获取对象的字符串表示形式的标准方 式。Python 提供了两种方式。 repr()   以便于开发者理解的方式返回对象的字符串表示形式。str()   以便于用户理解的方式返回对象的字符串表示形式。 正如你所知&#xff0c;我们要实现_…

springboot配置tomcat端口的方法

在Spring Boot中配置Tomcat端口可通过以下方法实现&#xff1a; 配置文件方式 properties格式 在application.properties中添加&#xff1a;server.port8081YAML格式 在application.yml中添加&#xff1a;server:port: 8082多环境配置 创建不同环境的配置文件&#xff08;如app…

DeepSeek指令微调与强化学习对齐:从SFT到RLHF

后训练微调的重要性 预训练使大模型获得丰富的语言和知识表达能力,但其输出往往与用户意图和安全性需求不完全匹配。业内普遍采用三阶段训练流程:预训练 → 监督微调(SFT)→ 人类偏好对齐(RLHF)。预训练阶段模型在大规模语料上学习语言规律;监督微调利用人工标注的数据…

Maven 插件扩展点与自定义生命周期

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

ecmascript 第6版特性 ECMA-262 ES6

https://blog.csdn.net/zlpzlpzyd/article/details/146125018 在之前写的文章基础上&#xff0c;ES6在export和import的基础外&#xff0c;还有如下特性 特性说明let/const块级作用域变量声明>箭头函数Promise异步编程

CT重建笔记(五)—2D平行束投影公式

写的又回去了&#xff0c;因为我发现我理解不够透彻&#xff0c;反正想到啥写啥&#xff0c;尽量保证内容质量好简洁易懂 2D平行束投影公式 p ( s , θ ) ∫ ∫ f ( x , y ) δ ( x c o s θ y s i n θ − s ) d x d y p(s,\theta)\int \int f(x,y)\delta(x cos\theta ysi…

记一次缓存填坑省市区级联获取的操作

先说缓存是什么&#xff1f; 缓存主要是解决高并发&#xff0c;大数据场景下&#xff0c;热点数据快速访问。缓存的原则首先保证数据的准确和最终数据一致&#xff0c;其次是距离用户越近越好&#xff0c;同步越及时越好。 再说我们遇到的场景&#xff1a; 接手项目后&#…

无法加载文件 E:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本

遇到“无法加载文件 E:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本”这类错误&#xff0c;通常是因为你的 PowerShell 执行策略设置为不允许运行脚本。在 Windows 系统中&#xff0c;默认情况下&#xff0c;出于安全考虑&#xff0c;PowerShell 可能会阻止运行未…

OpenWebUI新突破,MCPO框架解锁MCP工具新玩法

大家好&#xff0c;Open WebUI 迎来重要更新&#xff0c;现已正式支持 MCP 工具服务器&#xff0c;但 MCP 工具服务器需由兼容 OpenAPI 的代理作为前端。mcpo 是一款实用代理&#xff0c;经测试&#xff0c;它能让开发者使用 MCP 服务器命令和标准 OpenAPI 服务器工具&#xff…

松下SMT贴片机选型与高效应用指南

内容概要 在电子制造领域&#xff0c;SMT贴片机作为核心生产设备&#xff0c;其选型与应用直接关系到企业产能与产品质量。本文聚焦松下SMT贴片机系列&#xff0c;通过系统性梳理设备选型逻辑与技术特性&#xff0c;为制造企业提供多维度的决策参考。重点涵盖主流机型性能参数…

计算机网络(1)——概述

1.计算机网络基本概念 1.1 什么是计算机网络 计算机网络的产生背景 在计算机网络出现之前&#xff0c;计算机之间都是相互独立的&#xff0c;每台计算机只能访问自身存储的数据&#xff0c;无法与其他计算机进行数据交换和资源共享。这种独立的计算机系统存在诸多局限性&#…

React学习(二)-变量

也是很无聊&#xff0c;竟然写这玩意&#xff0c;毕竟不是学术研究&#xff0c;普通工作没那么多概念性东西&#xff0c;会用就行╮(╯▽╰)╭ 在React中&#xff0c;变量是用于存储和管理数据的基本单位。根据其用途和生命周期&#xff0c;React中的变量可以分为以下几类&…