You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何让描述符的__get__方法返回另一个描述符?类方法场景解析

关于类方法__get__返回对象的__get__行为及描述符嵌套场景

首先,你观察到的类方法的这个行为是Python刻意设计的,和子类重写类方法的场景密切相关,下面分两部分解答你的问题:

一、类方法__get__返回对象的__get__:适用场景与子类重写的关系

类方法本身是classmethod类的实例,属于非数据描述符(只有__get__方法)。当你通过类或实例访问类方法时,会调用classmethod.__get__,返回一个绑定的类方法对象(method类型)——而这个绑定对象确实自带__get__方法,它的核心作用是支持类继承中的动态绑定适配。

举个实际例子看作用:

class K:
    @classmethod
    def cm(cls):
        print(f"当前类: {cls.__name__}")

class J(K):
    pass  # 暂不重写cm方法

# 获取父类K的绑定类方法
k_bound_cm = K.cm
k_bound_cm()  # 输出: 当前类: K

# 把这个绑定对象重新绑定到子类J
j_bound_cm = k_bound_cm.__get__(J)
j_bound_cm()  # 输出: 当前类: J

如果子类重写了类方法,这个机制依然能正确工作:

class J(K):
    @classmethod
    def cm(cls):
        print(f"子类J的方法: {cls.__name__}")

# 父类的绑定方法重新绑定到J时,调用的还是K的cm,但cls参数是J
k_bound_cm.__get__(J)()  # 输出: 当前类: J
# 而直接访问J.cm会调用子类的重写方法
J.cm()  # 输出: 子类J的方法: J

这个设计的核心场景是动态编程或框架开发:当你需要在运行时把父类的类方法引用绑定到任意子类上时,不需要重新从子类属性中获取,直接通过绑定对象的__get__就能快速得到适配子类的方法实例,确保cls参数始终是目标子类。

二、描述符的__get__返回另一个描述符的有用场景

让描述符的__get__返回另一个描述符,是Python中实现动态属性逻辑、功能组合、延迟绑定的常用技巧,典型场景包括:

1. 类/静态方法的继承适配

正如前面的classmethod,它的__get__返回的绑定方法本身是一个描述符,确保当子类继承父类的类方法时,访问子类的该方法能自动绑定到子类,而不是固定父类。staticmethod的实现逻辑类似,只是它的绑定对象__get__会忽略实例/类参数,始终返回原始函数。

2. 动态适配不同上下文的属性逻辑

比如你需要一个属性,根据访问它的实例类型返回不同的验证或处理逻辑:

class BaseValidator:
    def __get__(self, instance, owner):
        # 根据实例类型返回对应的验证描述符
        if isinstance(instance, User):
            return UserValidator()
        elif isinstance(instance, Admin):
            return AdminValidator()
        else:
            return DefaultValidator()

class UserValidator:
    def __get__(self, instance, owner):
        return f"User验证逻辑: {instance.username}"

class AdminValidator:
    def __get__(self, instance, owner):
        return f"Admin验证逻辑: {instance.admin_id}"

class User:
    validate = BaseValidator()
    def __init__(self, username):
        self.username = username

class Admin:
    validate = BaseValidator()
    def __init__(self, admin_id):
        self.admin_id = admin_id

u = User("alice")
print(u.validate)  # 输出: User验证逻辑: alice
a = Admin("admin001")
print(a.validate)  # 输出: Admin验证逻辑: admin001

3. 描述符的组合与装饰器链式调用

当你需要给一个方法同时应用多个描述符功能时,可以让外层描述符的__get__返回内层描述符,实现功能叠加。比如自定义一个日志描述符,配合classmethod使用:

class LogDescriptor:
    def __init__(self, func):
        self.func = func
    
    def __get__(self, instance, owner):
        # 返回一个classmethod描述符,同时保留日志逻辑
        def wrapped(cls, *args, **kwargs):
            print(f"调用方法: {self.func.__name__}")
            return self.func(cls, *args, **kwargs)
        return classmethod(wrapped).__get__(instance, owner)

class K:
    @LogDescriptor
    def cm(cls, a):
        return f"K.cm返回: {a}"

K.cm(123)
# 输出:
# 调用方法: cm
# K.cm返回: 123

4. 延迟初始化的描述符

对于一些初始化成本较高的描述符(比如需要连接数据库、加载大资源),可以让外层描述符的__get__在第一次访问时才初始化实际的描述符,之后直接返回缓存的实例:

class LazyDescriptor:
    def __init__(self, descriptor_cls, *args, **kwargs):
        self.descriptor_cls = descriptor_cls
        self.args = args
        self.kwargs = kwargs
        self._cached = None
    
    def __get__(self, instance, owner):
        if self._cached is None:
            self._cached = self.descriptor_cls(*self.args, **self.kwargs)
        # 返回实际的描述符,后续访问会直接走它的__get__
        return self._cached.__get__(instance, owner)

# 假设HeavyDescriptor是初始化成本很高的描述符
class HeavyDescriptor:
    def __init__(self):
        print("HeavyDescriptor初始化中...")
        # 模拟耗时操作
        import time
        time.sleep(1)
    
    def __get__(self, instance, owner):
        return "HeavyDescriptor返回值"

class MyClass:
    heavy_prop = LazyDescriptor(HeavyDescriptor)

obj = MyClass()
print(obj.heavy_prop)  # 第一次访问会触发初始化
print(obj.heavy_prop)  # 第二次直接返回缓存的结果

内容的提问来源于stack exchange,提问作者Toothpick Anemone

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:56:41