【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.pswp.cn/news/905941.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/905941.shtml
英文地址,請注明出處:http://en.pswp.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中的變量可以分為以下幾類&…