首页 新闻 会员 周边

类作为装饰器装饰别人

0
[已关闭问题] 关闭于 2026-06-18 16:49
# 装饰器类
class Decorator:
    def __init__(self, func):
        # 把被装饰的函数存起来
        self.func = func

    # 让Decorator实例可以像函数一样调用
    def __call__(self, *args, **kwargs):
        return self.func(*args, **kwargs)

# 普通类
class cls:
    # 用Decorator装饰实例方法method
    @Decorator
    def method(self, x, y):
        return x + y

c = cls()
print(c.method(3, 4))

这个c.method 拿到的是 Decorator 实例没看懂,,c是cls的实例,method是decorator的实例,c.method意思实例调用实例method,这是什么鬼,意思任何一个对象都能调用另一个对象,没看懂,这也是困惑所在

*Tesla*的主页 *Tesla* | 小虾三级 | 园豆:1762
提问于:2026-06-18 16:47
< >
分享
所有回答(1)
0

核心拆解:不是「对象调用对象」,是「属性查找」+「可调用对象」两个知识点混在一起了,分开讲你立刻懂

1. 先纠正你的误区:不是 c 调用了 method 对象

c.method(3,4) 分两步完全独立的操作:

第一步:c.method —— 只是查属性,和“调用”无关

c 是 cls 的实例,Python 找属性的规则:

1. 先看实例 c 自己身上有没有 method 属性;

2. 没有就去它所属的类 cls 身上找;

3. cls 类里写了 method = Decorator(原函数),所以 cls.method 是一个 Decorator 实例;

4. 因此 c.method = cls.method,拿到的就是这个 Decorator 实例对象。

👉 这一步仅仅是读取属性,只是把一个对象取出来,不存在“调用”动作。
举个极简类比:

class Box:
    pass
class A:
    obj = Box()  # A类里存了一个Box实例

a = A()
print(a.obj) # 等价 A.obj,取出Box实例

这里 a.obj 取出另一个类的实例,和你代码里 c.method 取出 Decorator 实例逻辑一模一样。
类里面可以存数字、字符串、列表,当然也能存别的类的实例,这只是普通属性存储,和能不能调用没关系。

第二步:(3,4) —— 对取出来的对象执行「调用」

正常只有函数能加 () 调用,但 Python 有个魔法方法 __call__:
只要一个类定义了 def __call__(self, ...),它的实例就变成可调用对象,可以用 对象() 的形式执行。

class Decorator:
def __call__(self, *args, **kwargs):
...

所以:
c.method → 拿到 Decorator 实例对象
c.method(3,4) → 等价 (拿到的Decorator实例).__call__(3,4)

2. 一句话区分两件事,别混为一谈

1. c.method:属性访问,只是取出存放在类里的另一个对象,任何对象都能作为类/实例的属性;

2. () 括号:调用操作,只有实现 __call__ 的对象才能加括号执行,不是所有对象都能被调用。

举反例:如果 Decorator 没有 call,代码直接报错

class Decorator:
    def __init__(self, func):
        self.func = func
    # 删掉 __call__

class cls:
    @Decorator
    def method(self,x,y):
        return x+y

c = cls()
c.method(3,4) # 报错:TypeError: 'Decorator' object is not callable

此时 c.method 依然是 Decorator 实例,但因为没有 __call__,不能加 () 调用,完美印证两件事是分开的。

3. 为什么普通不加装饰器的 c.method(3,4) 能自动传 self?

无装饰器时代码:

class cls:
    def method(self,x,y):
        return x+y

此时 cls.method 是原生函数,函数是描述符(function 实现了 __get__):
当通过实例 c.method 访问时,函数的 __get__ 会自动绑定实例 c 作为第一个参数 self,变成绑定方法。

但你现在 cls.method 不是原生函数,是 Decorator 自定义实例,没有实现描述符协议,Python 不会自动帮你把 c 塞进去当 self,所以参数缺失报错。

4. 完整流程复盘你的代码

1. 定义 @Decorator 装饰方法 → cls.method = Decorator(原始method函数);

2. c = cls() 创建实例,实例自身无 method 属性;

3. c.method 去类 cls 找到属性,得到 Decorator 实例;

4. 追加 (3,4),触发 Decorator 的 __call__(3,4);

5. __call__ 执行 self.func(3,4),也就是原始 method(3,4);

6. 原始 method 需要 self,x,y 三个参数,只传了两个,抛出参数缺失错误。

通俗大白话总结

• 类 cls 就像一个柜子,柜子里放了个盒子(Decorator实例,名叫method);

• c 是柜子的一把钥匙,c.method 只是用钥匙打开柜子,拿出里面那个盒子;

• 正常盒子不能操作,但这个盒子写了特殊规则(__call__),拿到盒子后可以直接按按钮 (3,4) 触发盒子内部逻辑;

• 盒子内部要调用原来的工具(原始method函数),但工具必须配套这把钥匙(self),你只给了数字,没给钥匙,工具没法运行。

*Tesla* | 园豆:1762 (小虾三级) | 2026-06-18 16:49

回答上面问题前预备知识:

语法上:对象.任意名字 统一都是「读取属性」操作,没有例外。
只是根据「属性存的值是什么东西」,我们分两种叫法:

1. 如果属性里存的是可调用函数/绑定方法 → 口语叫「拿到方法」

2. 如果属性里存数字、字符串、对象、装饰器实例等普通东西 → 叫「拿到普通属性」

1. 语法层面:. 永远是取属性,不分变量/函数

obj.name # 取属性name
obj.run # 取属性run

这两行做的事完全一样:沿着实例→类→父类找名字对应的存储值,纯查询,不执行代码。
. 这个运算符的作用就叫属性访问,不管后面是变量名还是函数名。

 

语义层面:区分“属性”和“方法”(人说话的叫法)

① 普通属性(值是数据)

类/实例属性存的是数据:数字、字符串、列表、自定义实例

class Cat:
age = 3 # 类属性,存数字
tom = Cat()
res = tom.age

tom.age 是属性访问,拿到数字3,我们只会说:拿到属性age。

② 方法(属性里存可调用对象)

类里用 def 定义的函数存在类上,通过实例.读取时,会自动包装成绑定方法对象:

class Cat:
def run(self):
print("跑")
tom = Cat()
res = tom.run

tom.run 依旧是属性访问,但这个属性存的值是一个可调用的绑定方法,所以口语简化说成:拿到方法run。

操作行为:.xxx → 固定叫「读取属性」

• 读取到的内容:

◦ 内容是可调用对象(function / bound method)→ 俗称方法

◦ 内容是数据、普通对象 → 俗称普通属性

支持(0) 反对(0) *Tesla* | 园豆:1762 (小虾三级) | 2026-06-19 11:28
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册