python 魔术方法详解

python 魔术方法详解

python 魔术方法详解

在这里插入图片描述

一、魔术方法详解

魔术方法(Magic Methods),也称为特殊方法(Special Methods)或双下方法(Dunder Methods),是 Python 中以双下划线开头和结尾的方法(例如 __init__, __str__)。它们不是让你直接调用的,而是由 Python 解释器在特定场景下自动调用的。它们为自定义类提供了与 Python 内置类型和操作符交互的能力,让你可以定义对象在各种操作下的行为。

1、核心概念

  1. 自动调用:你定义它们,但 Python 在特定事件发生时(如对象初始化、加法运算、打印对象等)自动调用它们。
  2. 增强功能:它们赋予你的类特殊的行为,使其可以像内置类型一样工作。
  3. 约定俗成:每个魔术方法都有特定的名称和预期的行为。遵循这些约定很重要。

2、常见的魔术方法分类及详解

2.1、构造与销毁 (__init__, __new__, __del__)

  • __del__(self):析构器方法。当对象的引用计数变为零(或垃圾回收器准备回收对象)时被调用。谨慎使用!因为 Python 的内存管理(引用计数和垃圾回收)使得很难精确预测 __del__ 何时被调用。通常不需要手动实现,更好的资源清理方式是使用上下文管理器 (with 语句)。

__new__(cls, [...]):在 __init__之前调用。它负责创建返回类的一个新实例。它接收类本身(cls)作为第一个参数。通常不需要覆盖,但在需要控制实例创建过程(如单例模式、不可变对象、子类化不可变类型)时很有用。

classSingleton: _instance =Nonedef__new__(cls):if cls._instance isNone: cls._instance =super().__new__(cls)return cls._instance s1 = Singleton() s2 = Singleton()print(s1 is s2)# 输出: True

__init__(self, [...]):最常用的魔术方法。在对象实例创建之后被调用。用于初始化新实例的属性(设置初始状态)。它接受传递给类构造函数的参数(通常是 __init__(self, param1, param2, ...))。

classPoint:def__init__(self, x, y): self.x = x self.y = y p = Point(3,4)# 调用 Point.__init__(p, 3, 4)

2.2、字符串表示 (__str__, __repr__)

  • __str__(self):当调用 str(object)print(object)format(object) 时调用。目的是返回一个对用户友好、可读性高的字符串表示,描述对象的内容。

__repr__(self):当调用 repr(object) 或在交互式解释器中直接输入对象名称时调用。目的是返回一个对开发者友好、明确的字符串表示,通常是一个可以用来重新创建该对象的有效 Python 表达式(如果可能的话)。如果 __str__ 未定义,__repr__ 会作为其默认回退。

classPoint:def__init__(self, x, y): self.x = x self.y = y def__str__(self):returnf"Point({self.x}, {self.y})"def__repr__(self):returnf"Point(x={self.x}, y={self.y})" p = Point(3,4)print(p)# 输出: Point(3, 4) (调用 __str__)print(repr(p))# 输出: Point(x=3, y=4) (调用 __repr__)

2.3、比较操作符 (__eq__, __ne__, __lt__, __le__, __gt__, __ge__)

  • 这些方法定义了对象在使用 ==, !=, <, <=, >, >= 操作符时的行为。
  • __eq__(self, other):定义 self == other 的行为。
  • __ne__(self, other):定义 self != other 的行为。默认实现通常是 not self == other
  • __lt__(self, other):定义 self < other 的行为。
  • __le__(self, other):定义 self <= other 的行为。
  • __gt__(self, other):定义 self > other 的行为。
  • __ge__(self, other):定义 self >= other 的行为。

如果只实现了其中几个(如 __lt__),Python 可能尝试用它们推导出其他比较操作(但这并不总是可靠)。使用 functools.total_ordering 装饰器可以自动补全缺失的比较方法,只需定义 __eq__ 和另一个(如 __lt__)。

classPoint:def__init__(self, x, y): self.x = x self.y = y def__eq__(self, other):return self.x == other.x and self.y == other.y def__lt__(self, other):# 定义小于,通常用于排序return(self.x **2+ self.y **2)<(other.x **2+ other.y **2) p1 = Point(1,2) p2 = Point(1,2) p3 = Point(3,4)print(p1 == p2)# Trueprint(p1 != p3)# True (默认使用 not __eq__)print(p1 < p3)# True

2.4、算术运算符重载 (__add__, __sub__, __mul__, __truediv__, __floordiv__, __mod__, __pow__, … 及其 __r*____i*__ 变体)

  • 这些方法定义了对象在使用 +, -, *, /, //, %, ** 等操作符时的行为。
  • __add__(self, other):定义 self + other 的行为。
  • __sub__(self, other):定义 self - other 的行为。
  • … 其他类似。
  • __radd__(self, other), __rsub__(self, other), …:定义 other + self, other - self 的行为(当 self 是操作符右侧的操作数时调用)。这用于处理 self 不支持与 other 类型直接运算的情况。

__iadd__(self, other), __isub__(self, other), …:定义 self += other, self -= other原地操作的行为。如果未实现,Python 会尝试使用 __add__ / __sub__ 等,并将结果赋值给 self

classVector:def__init__(self, x, y): self.x = x self.y = y def__add__(self, other):return Vector(self.x + other.x, self.y + other.y)def__iadd__(self, other):# 原地加法 += self.x += other.x self.y += other.y return self # 通常返回 selfdef__repr__(self):returnf"Vector({self.x}, {self.y})" v1 = Vector(1,2) v2 = Vector(3,4) v3 = v1 + v2 # 调用 v1.__add__(v2) -> Vector(4, 6) v1 += v2 # 调用 v1.__iadd__(v2) -> v1 变为 Vector(4, 6)

2.5、类型转换 (__int__, __float__, __bool__, __complex__)

  • 这些方法定义了对象在被传递给内置函数 int(), float(), bool(), complex() 时的行为。

__bool__(self):特别重要,定义了对象在布尔上下文中(如 if obj:)的真假值。如果未定义 __bool__,Python 会尝试调用 __len__()(如果定义了),非零长度则为真,否则为假。如果两者都未定义,则所有实例都被视为真。

classTruthy:def__bool__(self):returnTrueclassFalsy:def__bool__(self):returnFalseclassNoBoolButLen:def__len__(self):return0if Truthy():print("True")# 输出 Trueif Falsy():print("This won't")# 不输出if NoBoolButLen():print("This won't either")# 不输出 (__len__ 返回 0)

2.6、容器/序列模拟 (__len__, __getitem__, __setitem__, __delitem__, __contains__, __iter__, __reversed__, __next__)

  • 这些方法让自定义类表现得像列表、字典、集合等容器。
  • __len__(self):定义 len(obj) 的行为,返回容器中元素的个数。
  • __getitem__(self, key):定义 obj[key] 的行为,实现索引或键访问。
  • __setitem__(self, key, value):定义 obj[key] = value 的行为,实现索引或键赋值。
  • __delitem__(self, key):定义 del obj[key] 的行为,实现索引或键删除。
  • __contains__(self, item):定义 item in obj 的行为,检查元素是否存在。
  • __iter__(self):定义 iter(obj)for item in obj 的行为。应该返回一个实现了 __next__ 的迭代器对象(通常是 self 如果它自己实现了 __next__)。
  • __next__(self):定义迭代器对象在 next(iterator)for 循环中的下一个值的行为。当没有更多元素时,必须引发 StopIteration 异常。

__reversed__(self):定义 reversed(obj) 的行为,返回一个反向迭代器。

classMyList:def__init__(self, data): self.data =list(data)def__len__(self):returnlen(self.data)def__getitem__(self, index):return self.data[index]def__setitem__(self, index, value): self.data[index]= value def__delitem__(self, index):del self.data[index]def__contains__(self, item):return item in self.data def__iter__(self): self.current =0return self def__next__(self):if self.current <len(self.data): item = self.data[self.current] self.current +=1return item else:raise StopIteration my_list = MyList([1,2,3])print(len(my_list))# 3print(my_list[1])# 2 my_list[1]=20print(2in my_list)# Falseprint(20in my_list)# Truefor item in my_list:# 1, 20, 3print(item)

2.7、可调用对象 (__call__)

__call__(self, [...]):定义对象本身像函数一样被调用时的行为 obj(...)。这使得实例可以像函数一样使用。

classAdder:def__init__(self, base): self.base = base def__call__(self, x):return self.base + x add5 = Adder(5) result = add5(3)# 调用 add5.__call__(3)print(result)# 8

2.8、上下文管理器 (__enter__, __exit__)

  • 这些方法使对象可以用在 with 语句中,用于资源管理(如自动关闭文件)。
  • __enter__(self):在进入 with 块时调用,其返回值会赋值给 as 后的变量(如果有)。

__exit__(self, exc_type, exc_value, traceback):在退出 with 块时调用(无论正常退出还是异常退出)。它接收异常信息参数。如果返回 True 或真值,则表示异常已被处理,不会向外传播;返回 None 或假值,则异常会向外传播。

classManagedFile:def__init__(self, filename, mode): self.filename = filename self.mode = mode self.file=Nonedef__enter__(self): self.file=open(self.filename, self.mode)return self.filedef__exit__(self, exc_type, exc_value, traceback):if self.file: self.file.close()# 可以在这里处理异常,返回 True 表示已处理returnFalse# 让异常正常传播with ManagedFile('example.txt','w')as f: f.write('Hello, world!')# 文件在这里自动关闭

2.9、 属性访问控制 (__getattr__, __getattribute__, __setattr__, __delattr__)

  • __getattr__(self, name):当试图访问一个不存在的属性时调用。name 是属性名。可用于实现动态属性或惰性加载。
  • __getattribute__(self, name):当试图访问任何属性(无论是否存在)时调用。谨慎使用!很容易导致无限递归(访问 self.name 会再次触发 __getattribute__)。通常使用 super().__getattribute__(name) 来避免递归。覆盖它会显著影响性能。
  • __setattr__(self, name, value):当试图给任何属性赋值时调用。同样要注意避免递归(赋值 self.name = value 会再次触发 __setattr__)。通常使用 super().__setattr__(name, value) 或直接操作 __dict__

__delattr__(self, name):当试图删除一个属性时调用。

classLazy:def__init__(self): self._expensive_data =Nonedef__getattr__(self, name):if name =='expensive_data':if self._expensive_data isNone:print("Calculating expensive data...") self._expensive_data ="Computed Value"# 模拟计算return self._expensive_data raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'") lazy_obj = Lazy()print(lazy_obj.expensive_data)# 第一次访问会计算print(lazy_obj.expensive_data)# 第二次访问直接返回缓存

2.10、其他重要方法

  • __hash__(self):定义 hash(obj) 的行为,返回一个整数哈希值。如果定义了 __eq__,通常也应该定义 __hash__(除非对象是可变的)。哈希值应满足:相等的对象必须有相等的哈希值(但不要求不等的对象哈希值不同)。不可变对象通常需要实现。
  • __dir__(self):定义 dir(obj) 的行为,返回一个包含对象属性名称的列表。

3、总结

魔术方法是 Python 面向对象编程的核心机制之一。它们允许你:

  • 定制初始化 (__init__)
  • 定义对象如何被表示 (__str__, __repr__)
  • 控制比较行为 (__eq__, __lt__, …)
  • 重载运算符 (__add__, __sub__, …)
  • 模拟内置容器 (__len__, __getitem__, …)
  • 使对象可调用 (__call__)
  • 支持 with 语句 (__enter__, __exit__)
  • 管理属性访问 (__getattr__, __setattr__, …)
  • 提供类型转换 (__int__, __float__, __bool__)
  • 定义哈希值 (__hash__)

二、代码示例

import math classMatrix:def__init__(self, rows, cols=None, data=None):"""初始化矩阵 参数: rows: 行数或二维列表 cols: 列数(当rows为整数时) data: 初始数据(可选) """ifisinstance(rows,list):# 从二维列表创建 self.data = rows self.rows =len(rows) self.cols =len(rows[0])if self.rows >0else0else:# 指定行列创建 self.rows = rows self.cols = cols if cols isnotNoneelse rows self.data =[[0]*self.cols for _ inrange(self.rows)]if data:for i inrange(min(self.rows,len(data))):for j inrange(min(self.cols,len(data[i]))): self.data[i][j]= data[i][j]def__str__(self):"""美化输出矩阵""" max_len =max(len(str(x))for row in self.data for x in row)return"[\n"+"\n".join(" ["+", ".join(f"{x:>{max_len}}"for x in row)+"]"for row in self.data )+"\n]"def__repr__(self):"""返回可执行的表示形式"""returnf"Matrix({self.data})"def__add__(self, other):"""矩阵加法"""if self.rows != other.rows or self.cols != other.cols:raise ValueError("矩阵维度不匹配")return Matrix([[self.data[i][j]+ other.data[i][j]for j inrange(self.cols)]for i inrange(self.rows)])def__sub__(self, other):"""矩阵减法"""if self.rows != other.rows or self.cols != other.cols:raise ValueError("矩阵维度不匹配")return Matrix([[self.data[i][j]- other.data[i][j]for j inrange(self.cols)]for i inrange(self.rows)])def__mul__(self, other):"""矩阵乘法(支持标量和矩阵)"""ifisinstance(other,(int,float)):# 标量乘法return Matrix([[x * other for x in row]for row in self.data ])elifisinstance(other, Matrix):# 矩阵乘法if self.cols != other.rows:raise ValueError("矩阵维度不匹配") result = Matrix(self.rows, other.cols)for i inrange(self.rows):for j inrange(other.cols): result[i, j]=sum( self[i, k]* other[k, j]for k inrange(self.cols))return result else:raise TypeError("不支持的乘法类型")def__rmul__(self, other):"""右乘标量"""ifisinstance(other,(int,float)):return self * other raise TypeError("不支持的乘法类型")def__matmul__(self, other):"""矩阵乘法运算符 @ (Python 3.5+)"""return self * other def__getitem__(self, index):"""索引访问"""ifisinstance(index,tuple)andlen(index)==2: i, j = index return self.data[i][j]elifisinstance(index,int):return self.data[index]raise IndexError("无效索引")def__setitem__(self, index, value):"""索引赋值"""ifisinstance(index,tuple)andlen(index)==2: i, j = index self.data[i][j]= value elifisinstance(index,int): self.data[index]= value else:raise IndexError("无效索引")def__eq__(self, other):"""矩阵相等比较"""return( self.rows == other.rows and self.cols == other.cols andall( self.data[i][j]== other.data[i][j]for i inrange(self.rows)for j inrange(self.cols)))def__abs__(self):"""计算行列式"""if self.rows != self.cols:raise ValueError("只有方阵才有行列式")return self._determinant(self.data)def_determinant(self, matrix):"""递归计算行列式""" n =len(matrix)if n ==1:return matrix[0][0]if n ==2:return matrix[0][0]*matrix[1][1]- matrix[0][1]*matrix[1][0] det =0for j inrange(n): minor =[[matrix[i][k]for k inrange(n)if k != j]for i inrange(1, n)] sign =(-1)** j det += sign * matrix[0][j]* self._determinant(minor)return det def__invert__(self):"""矩阵转置"""return Matrix([[self.data[j][i]for j inrange(self.rows)]for i inrange(self.cols)])def__call__(self, vector):"""将矩阵作为线性变换函数"""ifnotisinstance(vector,list)orlen(vector)!= self.cols:raise ValueError("输入必须是长度为矩阵列数的列表")return[sum(self[i, j]* vector[j]for j inrange(self.cols))for i inrange(self.rows)]# 示例用法if __name__ =="__main__":# 创建矩阵 A = Matrix([[1,2,3],[4,5,6],[7,8,9]]) B = Matrix([[9,8,7],[6,5,4],[3,2,1]])print("矩阵 A:")print(A)print("\n矩阵 B:")print(B)print("\n矩阵加法 A + B:")print(A + B)print("\n矩阵乘法 A * B:")print(A * B)print("\n使用 @ 运算符的矩阵乘法 A @ B:")print(A @ B)print("\n标量乘法 2 * A:")print(2* A)print("\n矩阵转置 ~A:")print(~A)print("\n矩阵行列式 |A| =",abs(A))print("\n矩阵作为线性变换 A([1, 2, 3]) =", A([1,2,3]))print("\n矩阵相等比较 A == B:", A == B)

运行结果:

矩阵 A:[[1,2,3][4,5,6][7,8,9]] 矩阵 B:[[9,8,7][6,5,4][3,2,1]] 矩阵加法 A + B:[[10,10,10][10,10,10][10,10,10]] 矩阵乘法 A * B:[[30,24,18][84,69,54][138,114,90]] 使用 @ 运算符的矩阵乘法 A @ B:[[30,24,18][84,69,54][138,114,90]] 标量乘法 2* A:[[2,4,6][8,10,12][14,16,18]] 矩阵转置 ~A:[[1,4,7][2,5,8][3,6,9]] 矩阵行列式 |A|=0 矩阵作为线性变换 A([1,2,3])=[14,32,50] 矩阵相等比较 A == B: False PS C:\Users\徐鹏\Desktop\新建文件夹 (2)>
在这里插入图片描述
在这里插入图片描述

Read more

安装 启动 使用 Neo4j的超详细教程

安装 启动 使用 Neo4j的超详细教程

最近在做一个基于知识图谱的智能生成项目。需要用到Neo4j图数据库。写这篇文章记录一下Neo4j的安装及其使用。 一.Neo4j的安装 1.首先安装JDK,配环境变量。(参照网上教程,很多) Neo4j是基于Java的图形数据库,运行Neo4j需要启动JVM进程,因此必须安装JAVA SE的JDK。从Oracle官方网站下载 Java SE JDK。我使用的版本是JDK1.8 2.官网上安装neo4j。 官方网址:https://neo4j.com/deployment-center/  在官网上下载对应版本。Neo4j应用程序有如下主要的目录结构: bin目录:用于存储Neo4j的可执行程序; conf目录:用于控制Neo4j启动的配置文件; data目录:用于存储核心数据库文件; plugins目录:用于存储Neo4j的插件; 3.配置环境变量 创建主目录环境变量NEO4J_HOME,并把主目录设置为变量值。复制具体的neo4j文件地址作为变量值。 配置文档存储在conf目录下,Neo4j通过配置文件neo4j.conf控制服务器的工作。默认情况下,不需

企业微信群机器人Webhook配置全攻略:从创建到发送消息的完整流程

企业微信群机器人Webhook配置全攻略:从创建到发送消息的完整流程 在数字化办公日益普及的今天,企业微信作为国内领先的企业级通讯工具,其群机器人功能为团队协作带来了极大的便利。本文将手把手教你如何从零开始配置企业微信群机器人Webhook,实现自动化消息推送,提升团队沟通效率。 1. 准备工作与环境配置 在开始创建机器人之前,需要确保满足以下基本条件: * 企业微信账号:拥有有效的企业微信管理员或成员账号 * 群聊条件:至少包含3名成员的群聊(这是创建机器人的最低人数要求) * 网络环境:能够正常访问企业微信服务器 提示:如果是企业管理员,建议先在"企业微信管理后台"确认机器人功能是否已对企业开放。某些企业可能出于安全考虑会限制此功能。 2. 创建群机器人 2.1 添加机器人到群聊 1. 打开企业微信客户端,进入目标群聊 2. 点击右上角的群菜单按钮(通常显示为"..."或"⋮") 3. 选择"添加群机器人"选项 4.

Flowise物联网融合:与智能家居设备联动的应用设想

Flowise物联网融合:与智能家居设备联动的应用设想 1. Flowise:让AI工作流变得像搭积木一样简单 Flowise 是一个真正把“AI平民化”落地的工具。它不像传统开发那样需要写几十行 LangChain 代码、配置向量库、调试提示词模板,而是把所有这些能力打包成一个个可拖拽的节点——就像小时候玩乐高,你不需要懂塑料怎么合成,只要知道哪块该拼在哪,就能搭出一座城堡。 它诞生于2023年,短短一年就收获了45.6k GitHub Stars,MIT协议开源,意味着你可以放心把它用在公司内部系统里,甚至嵌入到客户交付的产品中,完全不用担心授权问题。最打动人的不是它的技术多炫酷,而是它真的“不挑人”:产品经理能搭出知识库问答机器人,运营同学能配出自动抓取竞品文案的Agent,连刚学Python两周的实习生,也能在5分钟内跑通一个本地大模型的RAG流程。 它的核心逻辑很朴素:把LangChain里那些抽象概念——比如LLM调用、文档切分、向量检索、工具调用——变成画布上看得见、摸得着的方块。你拖一个“Ollama LLM”节点,再拖一个“Chroma Vector

OpenClaw配置Bot接入飞书机器人+Kimi2.5

OpenClaw配置Bot接入飞书机器人+Kimi2.5

上一篇文章写了Ubuntu_24.04下安装OpenClaw的过程,这篇文档记录一下接入飞书机器+Kimi2.5。 准备工作 飞书 创建飞书机器人 访问飞书开放平台:https://open.feishu.cn/app,点击创建应用: 填写应用名称和描述后就直接创建: 复制App ID 和 App Secret 创建成功后,在“凭证与基础信息”中找到 App ID 和 App Secret,把这2个信息复制记录下来,后面需要配置到openclaw中 配置权限 点击【权限管理】→【开通权限】 或使用【批量导入/导出权限】,选择导入,输入以下内容,如下图 点击【下一步,确认新增权限】即可开通所需要的权限。 配置事件与回调 说明:这一步的配置需要先讲AppId和AppSecret配置到openclaw成功之后再设置订阅方式,