为何类的属性和类实例的属性不相等?

class Foo:   #描述符    def __get__(self, instance, owner):
        pass
    def __set__(self, instance, value):
        pass
class Bar:
    x = Foo()      #把描述符代理一个类的属性
    def __init__(self,n):  
        self.x = n
上面的代码准备好了两个类,请看表演
>>> y=Bar(1)>>> y.x
>>> Bar.x
>>> Bar.x == y.x
True
接着来
>>> Bar.x = 2>>> y.x
2
>>> Bar.x 
2
>>> y=Bar(3)
>>> y.x
3
>>> Bar.x
> 2
回答:
class Foo:   #描述符    def __get__(self, instance, owner):
        print("tigger __get__")    # <- Add print for debugging
        pass
    def __set__(self, instance, value):
        print("tigger __set__")    # <- Add print for debugging
        pass
class Bar:
    x = Foo()      #把描述符代理一个类的属性
    def __init__(self,n):  
        self.x = n
以你第一次为例子:
>>> y=Bar(1)tigger __set__
>>> y.x
tigger __get__
>>> Bar.x
tigger __get__
>>> Bar.x == y.x
tigger __get__
tigger __get__
True
能明显看到,你这次的实例化使用,都在使用 Foo 这个描述符的 __get__ 和 __set__ 方法,由于这俩方法,你都用的pass,所以 Bar.x 和 y.x 返回都是None,故而相等。
来看第二次:
>>> Bar.x = 2>>> y.x
2
>>> Bar.x 
2
>>> y=Bar(3)
>>> y.x
3
>>> Bar.x
> 2
设置的print均没有打印,这是因为原本类属性 x 是描述符类 Foo,但是 Bar.x = 2 将类属性 x 赋值给了整数2,这个操作更改了类属性 x 的id和原本数据类型,这时候 类.x 和 实例.x 看上去虽然都是同一个属性名称 x,但内存地址已经不同了。
所以当你将y重新实例化的时候,y = Bar(3) 里面 Bar.x 不受实例属性的value变化而影响,所以 y.x 此时是3,但 Bar.x 依旧是旧值2。
这里面有个关键点就是,描述符在里面起到的作用。当类的属性值是描述符的时候,类和实例拥有相同的命名空间x,这时候描述符的lookup优先级高于类和实例的dictionary查找顺序,均以描述符为最高等级的查询机制,所以你的第一步 y = Bar(1) 的时候,Bar.x 和 y.x 其实都是因为描述符的存在,他们二者联系在了一起,彼此相通。但当你第二步,将 Bar.x 替换成常规数据类型的时候,类的属性和实例的属性就分开了,彼此互不干扰。
- Instance lookup scans through a chain of namespaces giving data descriptors the highest priority, followed by instance variables, then non-data descriptors, then class variables, and lastly __getattr__() if it is provided.
 - If a descriptor is found, it is invoked with desc.__get__(None, A)「A is a class」.
 - Data descriptors always override instance dictionaries.
 
你可以看看官方文档中对描述符的调用描述 -> descriptor-invocation
回答:
Bar.x 是类属性,它是一个描述符对象 Foo() 的实例。在实例化 y = Bar(1) 时,我们将 self.x 设置为 1。
此时,y.x 是实例 y 的属性。但是,由于我们没有定义 Foo 类的 get 方法,所以 y.x 的访问会调用 Foo 类的 get 方法,该方法中并没有返回任何值,因此输出为 None。
同时,当我们打印 Bar.x 时,输出为 <__main__.Foo object at 0x000001>。这是因为 Bar.x 是类属性,它是 Foo() 的实例,所以输出的是 Foo() 对象的内存地址。
接下来,我们执行以下代码:
Bar.x = 2print(y.x)  # 输出:2
print(Bar.x)  # 输出:2
我们修改了类属性 Bar.x 的值为 2。当我们打印 y.x 时,输出为 2,这是因为此时 y.x 是实例属性,而实例属性会覆盖类属性的值。
同时,当我们打印 Bar.x 时,输出也为 2,这是因为类属性 Bar.x 的值已被修改为 2。
接下来,我们重新创建了一个实例 y=Bar(3):
y = Bar(3)print(y.x)  # 输出:3
print(Bar.x)  # 输出:2
我们将实例 y 的属性 x 设置为 3。此时,y.x 是实例属性,值为 3。而 Bar.x 仍然是类属性,值为 2,没有受到实例属性的影响。
所以,最终输出为 y.x=3 和 Bar.x=2。这说明类实例的属性和类属性是不相等的。
以上是 为何类的属性和类实例的属性不相等? 的全部内容, 来源链接: utcz.com/p/938964.html


