Python类方法:动态绑定与身份识别详解
本文深入解析Python类方法的动态绑定特性,揭示了其背后由描述符协议驱动的机制。通过对比方法对象与底层函数对象(`__func__`属性),阐明了为何每次访问类方法都会生成新的对象。针对类继承和动态排除方法等实际场景中遇到的问题,提供了基于`__func__`或`__name__`属性的有效比较策略,旨在帮助开发者精准识别和操作类方法,避免常见的身份识别错误。同时,文章还优化了类方法的调用方式,强调直接使用绑定后的方法对象进行调用,从而构建更健壮、易于维护的Python面向对象代码。掌握Python类方法的动态特性和描述符协议,是提升Python编程技能的关键一步。

Python方法对象的动态性与描述符协议
在Python中,当我们访问一个类方法(或其他类型的绑定方法,如实例方法)时,Python并不会返回一个静态的、预先存在的对象。相反,它会动态地创建一个新的“方法对象”。这个行为是Python的“描述符协议”(Descriptor Protocol)在起作用。
@classmethod装饰器实际上是将一个普通函数转换成了一个classmethod描述符。当通过类(如Parent.func1)或实例(如p1.func1)访问这个描述符时,描述符的__get__方法会被调用,它会返回一个新的、绑定到相应类或实例的方法对象。由于每次访问都会触发__get__方法并生成新对象,因此即使是多次访问同一个类方法,它们在内存中的身份(即id()值)也是不同的。
例如,考虑以下代码:
class Parent:
@classmethod
def func1(cls):
print("hello func1")
class Child(Parent):
pass
# 每次访问 Parent.func1 都会得到不同的方法对象
print(f"Parent.func1 的 ID: {id(Parent.func1)}")
print(f"Parent.func1 的 ID: {id(Parent.func1)}")
print(f"Child.func1 的 ID: {id(Child.func1)}")
# 比较两个方法对象的身份,结果为 False
print(f"Parent.func1 is Parent.func1: {Parent.func1 is Parent.func1}")
print(f"Parent.func1 is Child.func1: {Parent.func1 is Child.func1}")运行上述代码,你会发现每次id()的输出都不同,并且is运算符的比较结果都是False。这表明Parent.func1和Child.func1,甚至两次访问Parent.func1所得到的方法对象,都不是同一个对象。
__func__属性:底层函数的统一标识
尽管方法对象本身是动态创建的,但它们所封装的底层函数对象却是唯一的。这个底层函数可以通过方法对象的__func__属性来访问。对于同一个逻辑函数,无论它是通过父类还是子类访问,其__func__属性都指向同一个函数对象。
class Parent:
@classmethod
def func1(cls):
print("hello func1")
class Child(Parent):
pass
# 比较底层函数对象的身份,结果为 True
print(f"Parent.func1.__func__ is Child.func1.__func__: {Parent.func1.__func__ is Child.func1.__func__}")这个特性对于需要识别方法身份的场景至关重要。
原有代码问题分析
在提供的原始代码中,NO_CALCULATE列表存储的是Parent.func1这个特定的方法对象。然而,在calculate_kpis方法中,循环遍历CALCULATE列表时,每次func变量都会获取一个新的方法对象。
class Parent:
@classmethod
def func1(cls):
print("hello func1")
# ... 其他方法 ...
CALCULATE = [func1, func2, func3]
NO_CALCULATE = []
@classmethod
def calculate_kpis(cls):
for func in cls.CALCULATE:
# 这里的 func 是一个新创建的方法对象
# 而 cls.NO_CALCULATE 中存储的也是一个方法对象
# 由于身份不同,func not in cls.NO_CALCULATE 总是为 True
if func not in cls.NO_CALCULATE:
func.__get__(cls)() # 调用方式问题,将在下文优化
class Child(Parent):
NO_CALCULATE = [Parent.func1] # 这里存储的是 Parent.func1 的一个特定方法对象
if __name__ == "__main__":
p1 = Child()
p1.calculate_kpis()当Child.calculate_kpis被调用时,cls.CALCULATE中的func1(即Child.func1)是一个动态生成的新方法对象。而cls.NO_CALCULATE中存储的是Parent.func1在定义Child类时的一个特定方法对象。由于这两个方法对象在内存中的身份不同,func not in cls.NO_CALCULATE的判断始终为True,导致func1仍然被执行。
解决方案与代码优化
要正确地实现方法的排除逻辑,我们需要比较方法对象的某种稳定标识,而不是它们动态生成的对象身份。有两种推荐的解决方案:
1. 比较底层函数对象 (__func__)
通过比较方法对象的__func__属性,我们可以判断它们是否代表同一个逻辑函数。
class Parent:
@classmethod
def func1(cls):
print("hello func1 from Parent")
@classmethod
def func2(cls):
print("hello func2 from Parent")
@classmethod
def func3(cls):
print("hello func3 from Parent")
# CALCULATE 存储的是方法对象,其 __func__ 属性指向底层函数
CALCULATE = [func1, func2, func3]
NO_CALCULATE_FUNCS = [] # 存储要排除的底层函数对象
@classmethod
def calculate_kpis(cls):
for func_obj in cls.CALCULATE:
# 比较 func_obj 的底层函数是否在排除列表中
if func_obj.__func__ not in cls.NO_CALCULATE_FUNCS:
func_obj(cls) # 优化后的调用方式
class Child(Parent):
# 将 Parent.func1 的底层函数添加到排除列表
NO_CALCULATE_FUNCS = [Parent.func1.__func__]
if __name__ == "__main__":
print("--- 使用 __func__ 比较 ---")
p1 = Child()
p1.calculate_kpis()
# 预期输出:
# hello func2 from Parent
# hello func3 from Parent2. 比较方法名称 (__name__)
更简洁且通常更推荐的方法是直接比较方法名称字符串。这种方法不仅可以避免方法对象的动态性问题,而且在某些场景下(例如,当方法来自不同的基类但名称相同,且你希望排除所有同名方法时)更为灵活。
class Parent:
@classmethod
def func1(cls):
print("hello func1 from Parent")
@classmethod
def func2(cls):
print("hello func2 from Parent")
@classmethod
def func3(cls):
print("hello func3 from Parent")
CALCULATE = [func1, func2, func3]
NO_CALCULATE_NAMES = [] # 存储要排除的方法名称字符串
@classmethod
def calculate_kpis(cls):
for func_obj in cls.CALCULATE:
# 比较方法名称字符串是否在排除列表中
if func_obj.__name__ not in cls.NO_CALCULATE_NAMES:
func_obj(cls) # 优化后的调用方式
class Child(Parent):
# 将要排除的方法名称字符串添加到排除列表
NO_CALCULATE_NAMES = ["func1"]
if __name__ == "__main__":
print("\n--- 使用 __name__ 比较 ---")
p1 = Child()
p1.calculate_kpis()
# 预期输出:
# hello func2 from Parent
# hello func3 from Parent3. 优化类方法调用
原始代码中使用了func.__get__(cls)()来调用类方法。虽然这种方式在功能上等同于func(cls),但它暴露了描述符协议的底层细节,使得代码不够简洁和直观。
当func已经是一个通过描述符协议获取到的、绑定到cls的方法对象时,它本身就具有正确的调用行为。因此,直接使用func(cls)是更标准、更清晰的调用方式。
总结与注意事项
- 方法对象是动态的:在Python中,每次通过类或实例访问方法(包括类方法、实例方法)时,都会动态生成一个新的方法对象。这意味着method is method通常为False。
- __func__属性是底层函数标识:尽管方法对象不同,但它们封装的底层函数对象(通过__func__访问)是相同的,这提供了一个稳定的标识来判断两个方法是否逻辑上相同。
- 比较策略:
- __func__比较:适用于需要精确匹配底层函数对象的场景。
- __name__比较:通常更推荐,因为它简洁、直观,且能处理更广泛的排除需求(例如,按名称排除所有同名方法,无论其来源)。
- 优化调用:当已获取绑定方法对象时,直接使用method_obj(cls_or_instance)进行调用,避免不必要的__get__操作。
- 理解描述符协议:深入理解描述符协议对于掌握Python面向对象编程中的方法绑定机制至关重要。
通过理解Python方法对象的动态性及其底层工作原理,开发者可以避免常见的错误,并编写出更加健壮和可维护的代码。
文中关于的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《Python类方法:动态绑定与身份识别详解》文章吧,也可关注golang学习网公众号了解相关技术文章。
前端工具导入npm模块详解
- 上一篇
- 前端工具导入npm模块详解
- 下一篇
- Autoprefixer不加前缀?兼容性问题解决全攻略
-
- 文章 · python教程 | 13分钟前 | Python 数据结构 namedtuple 扑克牌 Card
- Pythonnamedtuple打造扑克牌玩法详解
- 291浏览 收藏
-
- 文章 · python教程 | 25分钟前 |
- PythonIQR方法检测异常值详解
- 478浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- Python除零错误解决方法详解
- 275浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- pip安装mysql-connector教程
- 116浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- Python中chr函数的使用方法与示例
- 260浏览 收藏
-
- 文章 · python教程 | 1小时前 | 继承 对象初始化 构造函数 __init__ super().__init__()
- Python\_\_init\_\_函数全解析
- 296浏览 收藏
-
- 文章 · python教程 | 2小时前 |
- ib_insyc获取交易合约ID方法详解
- 341浏览 收藏
-
- 文章 · python教程 | 2小时前 |
- Pandera多列校验:DataFrame数据验证教程
- 139浏览 收藏
-
- 文章 · python教程 | 2小时前 |
- GitLabCI/CD运行Pyglet测试教程
- 212浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3184次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3395次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3427次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4532次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3804次使用
-
- Flask框架安装技巧:让你的开发更高效
- 2024-01-03 501浏览
-
- Django框架中的并发处理技巧
- 2024-01-22 501浏览
-
- 提升Python包下载速度的方法——正确配置pip的国内源
- 2024-01-17 501浏览
-
- Python与C++:哪个编程语言更适合初学者?
- 2024-03-25 501浏览
-
- 品牌建设技巧
- 2024-04-06 501浏览

