cls是None吗
本帖最后由 VPython999 于 2023-6-10 10:12 编辑如图,这是小甲鱼《零基础入门学习Python》最新版P77上讲的,我想问下,最后d.__doc__执行的结果为什么没有打印"旺财"呢?self是__doc__,obj是d,cls应该没有传入吧,所以应该满足"cls is None"这个条件吧?是我理解有问题吗?请大神指教!
class MethodType:
def __init__(self,func,obj):
self.__func__=func
self.__self__=obj
def __call__(self,*args,**kwargs):
func=self.__func__
obj=self.__self__
print("小白")
return func(obj,*args,**kwargs)
class ClassMethod:
def __init__(self,f):
self.f=f
def __get__(self,obj,cls=None):
if cls is None:
print("旺财")
cls=type(obj)
if hasattr(type(self.f),'__get__'):
print(f"来福,type(self.f)->{type(self.f)}")
return self.f.__get__(cls,cls)
return MethodType(self.f,cls)
class D:
@ClassMethod
@property
def __doc__(cls):
return f"I love FishC.--from class {cls.__name__}"
d=D()
d.__doc__
来福,type(self.f)-><class 'property'>
'I love FishC.--from class D' 请问何时能审核通过啊? 本帖最后由 阿奇_o 于 2023-6-11 04:47 编辑
在__get__中(参数里),第三个参数其实 objtype,就是你绑定的 实例对象所属的类,所以cls除了参数初始化时为None,其后都不会是None,只能是 obj 的所属类。
ps: 这里要完全理解,估计不是一时半会的,因为它涉及了 魔法方法、描述符、装饰器,三个“难点”,其例子也有点“为了举例而举例”, 而且还混着来用。。
--------------------- 补充(能否理解就看你自己了) --------------------
一句话,两个关键点:
ClassMethod是 描述符(类) ,因为它实现了__get__。然后呢?然后,就是:
一句话:"要使用描述符 必须 实例化描述符,并 赋值给 一个类变量(名)"
关键点:一个是实例化,一个是赋值(绑定)。
- 在class D中,它借助 装饰器的功能,先是实例化了ClassMethod。(注意,@X类装饰器,在执行时,就是调用该类的默认的或你自定义的__call__方法。类装饰器时,就是其实例化,函数装饰器时,则是执行该函数。你的MethodType类中就是重写了__call__的例子)
- 然后,又借助@property类装饰器,把__doc__绑定到class D上(cls就是D),即让 __doc__成为一个 类变量(类属性)。不然,没法使用描述符,描述符实例 必须绑定/赋值 到类变量上。
所以这里 近似等价于:
class D:
__doc__ = ClassMethod()
小细节:__doc__原本也默认会绑定到类上,这里准确来说,不是让其“成为”,而是“重写”__doc__的功能(其默认是回显DocString,也就是你写在三个引号里的文档注释)
这段代码主要关注在Python的数据描述符(data descriptor)和方法描述符(method descriptor)概念上。当你使用d.__doc__时,Python的属性查找机制实际上会执行D.__doc__.__get__(d, D) 。
这里@ClassMethod和@property是两个装饰器,执行顺序是由下到上,所以@property会先于@ClassMethod执行。 property是 Python 的内置类型,它是一个数据描述符,数据描述符实现了__get__和__set__方法。而ClassMethod则实现了__get__方法,是一个方法描述符。
在Python的描述符协议中,对于一个类实例d ,当你通过d.__doc__访问时,Python会先查找类的字典,看有没有实现了 __get__ 的描述符,如果有,则调用它的 __get__ 方法。如果在类字典中找不到,Python会查找实例字典。而数据描述符(如 property )优先于实例字典,而方法描述符则优先于类字典。
所以当你执行d.__doc__ ,实际上会触发D.__doc__.__get__(d, D) 。在这个调用过程中, D.__doc__是ClassMethod的实例,并且它的f属性是property的实例。所以, D.__doc__.__get__(d, D)其实就是调用ClassMethod.__get__(self, obj, cls) ,其中, self是ClassMethod的实例, obj是d , cls是D 。
在ClassMethod.__get__方法内部, if cls is None:这个条件没有满足,因为传入的cls参数是D类,而不是None 。然后它检查self.f (也就是property的实例)是否有__get__方法,有的话就调用它。所以,“旺财”没有打印出来,而是打印出来了“来福,type(self.f)-><class 'property'>”,然后返回I love FishC.--from class D 。
这就是为什么你看到这样的输出的原因。 阿奇_o 发表于 2023-6-11 04:39
在__get__中(参数里),第三个参数其实 objtype,就是你绑定的 实例对象所属的类,所以cls除了参数初始化 ...
非常非常感谢您的回复,还想请教一下,您说“cls除了参数初始化时为None,其后都不会是None”,这个能举一个cls为None的例子吗?
还有就是,能麻烦您帮忙解答最后一个问题吗?为什么只要有@property,调用__doc__时就可以不加括号,这具体是怎么实现的呢?谢谢! isdkz 发表于 2023-6-11 08:03
这段代码主要关注在Python的数据描述符(data descriptor)和方法描述符(method descriptor)概念上。当你 ...
非常非常感谢您的回复,您跟楼上一样,都帮我大忙了,但是楼上回答的早,我就定楼上答案为最佳答案了哈{:5_109:} VPython999 发表于 2023-6-11 11:29
非常非常感谢您的回复,您跟楼上一样,都帮我大忙了,但是楼上回答的早,我就定楼上答案为最佳答案了哈{: ...
不客气{:5_109:} 本帖最后由 阿奇_o 于 2023-6-12 06:13 编辑
VPython999 发表于 2023-6-11 11:27
非常非常感谢您的回复,还想请教一下,您说“cls除了参数初始化时为None,其后都不会是None”,这个能举 ...
1. 因为None本身是个特殊的类型,其内部不包含任何数据。基本作用就是“先占个坑”。。
初始化时常用,就是告诉编译器,我这有个箱子,具体是属于什么类型、放什么东西/数据,等后面用到了在决定。
这个就不用举例子了吧,我问你吃饭了没,没有就是没有,None is None。。有就是有,吃了就是吃了,具体吃什么就看你自己了。。
2. 简单点说,就是利用装饰器的机制,把函数再套一两层逻辑,其本质就是嵌套函数(基本就两种:函数装饰器套函数,类装饰器套函数)。
另外一个关键就是:要把 函数当做一个对象(“一等对象”), func() 后面加括号才会执行其内部逻辑。
@property是“类装饰器”+“描述符/修饰器”的典型用法。
注意,property是Python内置的一个类,因为类class X天然可以be called 被调用(因为__call__),所以它天然能作为 装饰器也不奇怪。
@property 会把 点访问__doc__对象(任何X.__doc__ 这一写法/访问操作),都会根据“描述符协议”的__get__逻辑来走。
具体逻辑看官方手册Howtos指南的等价形式、或深究其源码细节。
这个property类,最大的好处就是简化代码,和返回一些需要动态查找或中间处理的属性数据。
总之,基本上,你只需要知道 @property 可以让一个方法,能像普通属性那样被访问,然后被执行,即可。(
(你甚至可以把 类的方法和普通属性,统一看做 属性Attributes。 你想想,一切不都是“对象”么?
研究对象,不就是给它起个“名”name,看看它有哪些“属性特征”,再如何“增删改查”这些属性特征数据。。)
Anyway,反正就是:对象那点事,以及逻辑与套娃的艺术。。
阿奇_o 发表于 2023-6-12 05:55
1. 因为None本身是个特殊的类型,其内部不包含任何数据。基本作用就是“先占个坑”。。
初始化时常用 ...
{:5_109:}懂啦,感谢您耐心的解释
页:
[1]