0

0

Python装饰器中使用类作为包装器时为何会丢失self引用?

碧海醫心

碧海醫心

发布时间:2026-01-04 14:13:02

|

530人浏览过

|

来源于php中文网

原创

Python装饰器中使用类作为包装器时为何会丢失self引用?

当用类实现装饰器的包装逻辑时,因未实现描述符协议(descriptor protocol),无法自动绑定实例方法中的`self`,导致调用时需手动传入对象;而函数式装饰器天然支持该协议,能正确完成方法绑定。

在 Python 中,实例方法的自动 self 绑定并非语法糖,而是由描述符协议(即 __get__ 方法)驱动的底层机制。当访问 obj.method 时,Python 会检查 method 是否实现了 __get__;若实现,则调用 method.__get__(obj, type(obj)),返回一个已绑定 self 的可调用对象(如 bound method)。普通函数(function 类型)内置了这一行为,因此用函数作装饰器包装器(如 @decorator)时,返回的 wrapper 函数仍可被正确绑定。

但自定义类(如 broken_decorator 中的 Wrapper)默认不实现 __get__,因此 three.plus_ 查找时直接返回该类实例本身,而非绑定后的调用体。调用 three.plus_(4) 实际执行的是 Wrapper().__call__(three, 4) —— 看似合理,却因 __call__ 的第一个参数被误认为 self(即 Wrapper 实例),而原方法期望的 MyClass 实例 three 反被当作 y 参数,造成语义错乱。示例中 three.plus_(MyClass(4), 4) 能“运行”,恰恰暴露了这种失配:你被迫显式传入 MyClass(4) 作为第一个参数,它被 wrapper.__call__ 当作 my_class_self,从而输出 4 + 4 = 8,完全偏离预期。

✅ 正确修复方式是让包装类实现描述符协议:

def broken_decorator(func):
    class Wrapper:
        def __call__(self, my_class_self, y):
            return func(my_class_self, y)

        def __get__(self, obj, objtype=None):
            if obj is None:  # 访问类属性时返回自身(如 MyClass.plus_)
                return self
            # 返回一个绑定 obj 的代理对象
            from functools import partial
            return partial(self.__call__, obj)

    return Wrapper()

然而,正如答案所强调:这并非推荐做法。手动实现 __get__ 易出错(需区分实例/类访问、处理 staticmethod/classmethod 边界)、性能更低,且绝大多数场景下,闭包函数已足够强大:

Tome
Tome

先进的AI智能PPT制作工具

下载

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

def decorator_with_state(func):
    # 用闭包保存状态,无需类
    call_count = 0
    def wrapper(self, y):
        nonlocal call_count
        call_count += 1
        print(f"[Call #{call_count}]")
        return func(self, y)
    return wrapper

? 总结:

  • ✅ 优先使用函数式装饰器(含闭包),简洁、高效、符合 Python 惯例;
  • ⚠️ 仅当必须封装复杂状态机、需多态接口或深度定制调用生命周期时,才考虑带 __get__ 的描述符类;
  • ❌ 切勿为“看起来更面向对象”而盲目用类替代函数——Python 的描述符机制早已为函数优化就绪,强行绕过只会增加维护成本。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

58

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

63

2025.11.27

java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

27

2025.11.27

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1926

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

656

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2399

2025.12.29

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

47

2026.01.19

go语言闭包相关教程大全
go语言闭包相关教程大全

本专题整合了go语言闭包相关数据,阅读专题下面的文章了解更多相关内容。

152

2025.07.29

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.5万人学习

Django 教程
Django 教程

共28课时 | 4.9万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号