为什么 Python 中的属性查找是这样设计的(优先链)?
Why attribute lookup in Python is designed this way (precedence chain)?
我刚刚 运行 进入了 Python 中的描述符,我对“__get__、__set__、[=24 中的描述符协议有了一些想法=]",它在包装方法方面确实做得很好。
但是,在 the protocol 中还有其他规则:
Data and non-data descriptors differ in how overrides are calculated with respect to entries in an instance’s dictionary. If an instance’s dictionary has an entry with the same name as a data descriptor, the data descriptor takes precedence. If an instance’s dictionary has an entry with the same name as a non-data descriptor, the dictionary entry takes precedence.
我没明白,只用 classic 方式查找是不是可以(实例字典 -> class 字典 -> 基础 class字典)?
如果以这种方式实现,数据描述符可以由实例持有,并且描述符本身不必持有 weakrefdict
来持有所有者的不同实例的值。
为什么要将描述符放入查找链中?还有为什么数据描述符放在最开始?
问题是超载之一。假设您有一个 Descriptor
class,并且您将对象的一个属性设置为该对象的一个实例 class:
class Descriptor:
...
def __get__(self, parent, type=None):
return 1
class MyObject:
def __init__(self):
self.foo = Descriptor()
mobj = MyObject()
在这种情况下,您有一个 non-data 描述符。由于 getter.
,任何访问 mobj.foo
的代码都将得到结果 1
但是假设您尝试存储那个属性?会发生什么?
答案:一个简单的条目将添加到实例字典中,mobj.foo
将指向存储的任何值。
在这种情况下,如果您随后从 mobj.foo
读取,您会得到哪个值? get函数返回的'1',或者字典中列出的recently-stored"live"值?
对了!在出现冲突的情况下,描述符会悄无声息地消失,剩下的就是检索您存储的内容。
首先 classic 方式(实际上并没有太大变化)不是你描述的那样。实际上,从这个意义上讲,没有基 class,基 classes 只是在 class 创建期间使用的东西。 classic 查找首先在实例中查找,然后在 class.
中查找
引入描述符的原因是允许以更简洁的方式自定义属性访问。 classic 方式依赖于可查找的函数来设置和获取属性。新方法还允许使用 @property
装饰器定义属性。
现在我们可以区分数据和 non-data(或 RW 和 RO)描述符。第一个应该注意的是,无论您尝试何种类型的访问(无论是读取、写入还是删除),都进行相同的查找是合理的:
描述符优先于 RO-descriptors 的原因是,如果您有 RO 描述符,您的意图通常是该属性应该是只读的。这意味着在这种情况下使用描述符是正确的。
另一方面,如果您有 RW-descriptor,使用 __dict__
条目存储实际数据会很有用。
还应注意,描述符正确放置在 class 而不是实例中(如果属性查找使用该方法找到对象,则属性查找会自动调用 __get__
)。
为什么不是另一种方式是因为如果将描述符放在实例中,您可能希望该属性实际引用描述符而不是描述符让您认为它是什么(通过调用 __get__
在上面)。例如:
class D:
def __get__(self):
return None
class C:
pass
o = C()
d = D()
o.fubar = d
现在最后的陈述可能是我们实际上将 D()
存储在 o.fubar
中,目的是 o.fubar
到 return d
而不是调用 d.__get__()
会 return None
.
让我们看一个例子:
class GetSetDesc(object):
def __init__(self, value):
self.value=value
def __get__(self, obj, objtype):
print("get_set_desc: Get")
return self.value
def __set__(self, obj, value):
print("get_set_desc: Set")
self.value=value
class SetDesc(object):
def __init__(self, value):
self.value=value
def __set__(self, obj, value):
print("set_desc: Set")
self.value=value
class GetDesc(object):
def __init__(self, value):
self.value=value
def __get__(self, obj, objtype):
print("get_desc: Get")
return self.value
class Test1(object):
attr=10
get_set_attr=10
get_set_attr=GetSetDesc(5)
set_attr=10
set_attr=SetDesc(5)
get_attr=10
get_attr=GetDesc(5)
class Test2(object):
def __init__(self):
self.attr=10
self.get_set_attr=10
self.get_set_attr=GetSetDesc(5)
self.set_attr=10
self.set_attr=SetDesc(5)
self.get_attr=10
self.get_attr=GetDesc(5)
class Test3(Test1):
def __init__(self):
#changing values to see differce with superclass
self.attr=100
self.get_set_attr=100
self.get_set_attr=GetSetDesc(50)
self.set_attr=100
self.set_attr=SetDesc(50)
self.get_attr=100
self.get_attr=GetDesc(50)
class Test4(Test1):
pass
print("++Test 1 Start++")
t=Test1()
print("t.attr:", t.attr)
print("t.get_set_desc:", t.get_set_attr)
print("t.set_attr:", t.set_attr)
print("t.get_attr:", t.get_attr)
print("Class dict attr:", t.__class__.__dict__['attr'])
print("Class dict get_set_attr:", t.__class__.__dict__['get_set_attr'])
print("Class dict set_attr:", t.__class__.__dict__['set_attr'])
print("Class dict get_attr:", t.__class__.__dict__['get_attr'])
#These will obviously fail as instance dict is empty here
#print("Instance dict attr:", t.__dict__['attr'])
#print("Instance dict get_set_attr:", t.__dict__['get_set_attr'])
#print("Instance dict set_attr:", t.__dict__['set_attr'])
#print("Instance dict get_attr:", t.__dict__['get_attr'])
t.attr=20
t.get_set_attr=20
t.set_attr=20
t.get_attr=20
print("t.attr:", t.attr)
print("t.get_set_desc:", t.get_set_attr)
print("t.set_attr:", t.set_attr)
print("t.get_attr:", t.get_attr)
print("Class dict attr:", t.__class__.__dict__['attr'])
print("Class dict get_set_attr:", t.__class__.__dict__['get_set_attr'])
print("Class dict set_attr:", t.__class__.__dict__['set_attr'])
print("Class dict get_attr:", t.__class__.__dict__['get_attr'])
print("Instance dict attr:", t.__dict__['attr'])
#Next two will fail,
#because the descriptor for those variables has __set__
#on the class itself which was called with value 20,
#so the instance is not affected
#print("Instance dict get_set_attr:", t.__dict__['get_set_attr'])
#print("Instance dict set_attr:", t.__dict__['set_attr'])
print("Instance dict get_attr:", t.__dict__['get_attr'])
print("++Test 1 End++")
print("++Test 2 Start++")
t2=Test2()
print("t.attr:", t2.attr)
print("t.get_set_desc:", t2.get_set_attr)
print("t.set_attr:", t2.set_attr)
print("t.get_attr:", t2.get_attr)
#In this test the class is not affected, so these will fail
#print("Class dict attr:", t2.__class__.__dict__['attr'])
#print("Class dict get_set_attr:", t2.__class__.__dict__['get_set_attr'])
#print("Class dict set_attr:", t2.__class__.__dict__['set_attr'])
#print("Class dict get_attr:", t2.__class__.__dict__['get_attr'])
print("Instance dict attr:", t2.__dict__['attr'])
print("Instance dict get_set_attr:", t2.__dict__['get_set_attr'])
print("Instance dict set_attr:", t2.__dict__['set_attr'])
print("Instance dict get_attr:", t2.__dict__['get_attr'])
t2.attr=20
t2.get_set_attr=20
t2.set_attr=20
t2.get_attr=20
print("t.attr:", t2.attr)
print("t.get_set_desc:", t2.get_set_attr)
print("t.set_attr:", t2.set_attr)
print("t.get_attr:", t2.get_attr)
#In this test the class is not affected, so these will fail
#print("Class dict attr:", t2.__class__.__dict__['attr'])
#print("Class dict get_set_attr:", t2.__class__.__dict__['get_set_attr'])
#print("Class dict set_attr:", t2.__class__.__dict__['set_attr'])
#print("Class dict get_attr:", t2.__class__.__dict__['get_attr'])
print("Instance dict attr:", t2.__dict__['attr'])
print("Instance dict get_set_attr:", t2.__dict__['get_set_attr'])
print("Instance dict set_attr:", t2.__dict__['set_attr'])
print("Instance dict get_attr:", t2.__dict__['get_attr'])
print("++Test 2 End++")
print("++Test 3 Start++")
t3=Test3()
print("t.attr:", t3.attr)
print("t.get_set_desc:", t3.get_set_attr)
print("t.set_attr:", t3.set_attr)
print("t.get_attr:", t3.get_attr)
#These fail, because nothing is defined on Test3 class itself, but let's see its super below
#print("Class dict attr:", t3.__class__.__dict__['attr'])
#print("Class dict get_set_attr:", t3.__class__.__dict__['get_set_attr'])
#print("Class dict set_attr:", t3.__class__.__dict__['set_attr'])
#print("Class dict get_attr:", t3.__class__.__dict__['get_attr'])
#Checking superclass
print("Superclass dict attr:", t3.__class__.__bases__[0].__dict__['attr'])
print("Superclass dict get_set_attr:", t3.__class__.__bases__[0].__dict__['get_set_attr'])
print("Superclass dict set_attr:", t3.__class__.__bases__[0].__dict__['set_attr'])
print("Superclass dict get_attr:", t3.__class__.__bases__[0].__dict__['get_attr'])
print("Instance dict attr:", t3.__dict__['attr'])
#Next two with __set__ inside descriptor fail, because
#when the instance was created, the value inside the descriptor in superclass
#was redefined via __set__
#print("Instance dict get_set_attr:", t3.__dict__['get_set_attr'])
#print("Instance dict set_attr:", t3.__dict__['set_attr'])
print("Instance dict get_attr:", t3.__dict__['get_attr'])
#The one above does not fail, because it doesn't have __set__ in
#descriptor in superclass and therefore was redefined on instance
t3.attr=200
t3.get_set_attr=200
t3.set_attr=200
t3.get_attr=200
print("t.attr:", t3.attr)
print("t.get_set_desc:", t3.get_set_attr)
print("t.set_attr:", t3.set_attr)
print("t.get_attr:", t3.get_attr)
#print("Class dict attr:", t3.__class__.__dict__['attr'])
#print("Class dict get_set_attr:", t3.__class__.__dict__['get_set_attr'])
#print("Class dict set_attr:", t3.__class__.__dict__['set_attr'])
#print("Class dict get_attr:", t3.__class__.__dict__['get_attr'])
#Checking superclass
print("Superclass dict attr:", t3.__class__.__bases__[0].__dict__['attr'])
print("Superclass dict get_set_attr:", t3.__class__.__bases__[0].__dict__['get_set_attr'])
print("Superclass dict set_attr:", t3.__class__.__bases__[0].__dict__['set_attr'])
print("Superclass dict get_attr:", t3.__class__.__bases__[0].__dict__['get_attr'])
print("Instance dict attr:", t3.__dict__['attr'])
#Next two fail, they are in superclass, not in instance
#print("Instance dict get_set_attr:", t3.__dict__['get_set_attr'])
#print("Instance dict set_attr:", t3.__dict__['set_attr'])
print("Instance dict get_attr:", t3.__dict__['get_attr'])
#The one above succeds as it was redefined as stated in prior check
print("++Test 3 End++")
print("++Test 4 Start++")
t4=Test4()
print("t.attr:", t4.attr)
print("t.get_set_desc:", t4.get_set_attr)
print("t.set_attr:", t4.set_attr)
print("t.get_attr:", t4.get_attr)
#These again fail, as everything defined in superclass, not the class itself
#print("Class dict attr:", t4.__class__.__dict__['attr'])
#print("Class dict get_set_attr:", t4.__class__.__dict__['get_set_attr'])
#print("Class dict set_attr:", t4.__class__.__dict__['set_attr'])
#print("Class dict get_attr:", t4.__class__.__dict__['get_attr'])
#Checking superclass
print("Superclass dict attr:", t4.__class__.__bases__[0].__dict__['attr'])
print("Superclass dict get_set_attr:", t4.__class__.__bases__[0].__dict__['get_set_attr'])
print("Superclass dict set_attr:", t4.__class__.__bases__[0].__dict__['set_attr'])
print("Superclass dict get_attr:", t4.__class__.__bases__[0].__dict__['get_attr'])
#Again, everything is on superclass, not the instance
#print("Instance dict attr:", t4.__dict__['attr'])
#print("Instance dict get_set_attr:", t4.__dict__['get_set_attr'])
#print("Instance dict set_attr:", t4.__dict__['set_attr'])
#print("Instance dict get_attr:", t4.__dict__['get_attr'])
t4.attr=200
t4.get_set_attr=200
t4.set_attr=200
t4.get_attr=200
print("t.attr:", t4.attr)
print("t.get_set_desc:", t4.get_set_attr)
print("t.set_attr:", t4.set_attr)
print("t.get_attr:", t4.get_attr)
#Class is not affected by those assignments, next four fail
#print("Class dict attr:", t4.__class__.__dict__['attr'])
#print("Class dict get_set_attr:", t4.__class__.__dict__['get_set_attr'])
#print("Class dict set_attr:", t4.__class__.__dict__['set_attr'])
#print("Class dict get_attr:", t4.__class__.__dict__['get_attr'])
#Checking superclass
print("Superclass dict attr:", t4.__class__.__bases__[0].__dict__['attr'])
print("Superclass dict get_set_attr:", t4.__class__.__bases__[0].__dict__['get_set_attr'])
print("Superclass dict set_attr:", t4.__class__.__bases__[0].__dict__['set_attr'])
print("Superclass dict get_attr:", t4.__class__.__bases__[0].__dict__['get_attr'])
#Now, this one we redefined it succeeds
print("Instance dict attr:", t4.__dict__['attr'])
#This one fails it's still on superclass
#print("Instance dict get_set_attr:", t4.__dict__['get_set_attr'])
#Same here - fails, it's on superclass, because it has __set__
#print("Instance dict set_attr:", t4.__dict__['set_attr'])
#This one succeeds, no __set__ to call, so it was redefined on instance
print("Instance dict get_attr:", t4.__dict__['get_attr'])
print("++Test 4 End++")
输出:
++Test 1 Start++
t.attr: 10
get_set_desc: Get
t.get_set_desc: 5
t.set_attr: <__main__.SetDesc object at 0x02896ED0>
get_desc: Get
t.get_attr: 5
Class dict attr: 10
Class dict get_set_attr: <__main__.GetSetDesc object at 0x02896EB0>
Class dict set_attr: <__main__.SetDesc object at 0x02896ED0>
Class dict get_attr: <__main__.GetDesc object at 0x02896EF0>
get_set_desc: Set
set_desc: Set
t.attr: 20
get_set_desc: Get
t.get_set_desc: 20
t.set_attr: <__main__.SetDesc object at 0x02896ED0>
t.get_attr: 20
Class dict attr: 10
Class dict get_set_attr: <__main__.GetSetDesc object at 0x02896EB0>
Class dict set_attr: <__main__.SetDesc object at 0x02896ED0>
Class dict get_attr: <__main__.GetDesc object at 0x02896EF0>
Instance dict attr: 20
Instance dict get_attr: 20
++Test 1 End++
++Test 2 Start++
t.attr: 10
t.get_set_desc: <__main__.GetSetDesc object at 0x028A0350>
t.set_attr: <__main__.SetDesc object at 0x028A0370>
t.get_attr: <__main__.GetDesc object at 0x028A0330>
Instance dict attr: 10
Instance dict get_set_attr: <__main__.GetSetDesc object at 0x028A0350>
Instance dict set_attr: <__main__.SetDesc object at 0x028A0370>
Instance dict get_attr: <__main__.GetDesc object at 0x028A0330>
t.attr: 20
t.get_set_desc: 20
t.set_attr: 20
t.get_attr: 20
Instance dict attr: 20
Instance dict get_set_attr: 20
Instance dict set_attr: 20
Instance dict get_attr: 20
++Test 2 End++
++Test 3 Start++
get_set_desc: Set
get_set_desc: Set
set_desc: Set
set_desc: Set
t.attr: 100
get_set_desc: Get
t.get_set_desc: <__main__.GetSetDesc object at 0x02896FF0>
t.set_attr: <__main__.SetDesc object at 0x02896ED0>
t.get_attr: <__main__.GetDesc object at 0x028A03F0>
Superclass dict attr: 10
Superclass dict get_set_attr: <__main__.GetSetDesc object at 0x02896EB0>
Superclass dict set_attr: <__main__.SetDesc object at 0x02896ED0>
Superclass dict get_attr: <__main__.GetDesc object at 0x02896EF0>
Instance dict attr: 100
Instance dict get_attr: <__main__.GetDesc object at 0x028A03F0>
get_set_desc: Set
set_desc: Set
t.attr: 200
get_set_desc: Get
t.get_set_desc: 200
t.set_attr: <__main__.SetDesc object at 0x02896ED0>
t.get_attr: 200
Superclass dict attr: 10
Superclass dict get_set_attr: <__main__.GetSetDesc object at 0x02896EB0>
Superclass dict set_attr: <__main__.SetDesc object at 0x02896ED0>
Superclass dict get_attr: <__main__.GetDesc object at 0x02896EF0>
Instance dict attr: 200
Instance dict get_attr: 200
++Test 3 End++
++Test 4 Start++
t.attr: 10
get_set_desc: Get
t.get_set_desc: 200
t.set_attr: <__main__.SetDesc object at 0x02896ED0>
get_desc: Get
t.get_attr: 5
Superclass dict attr: 10
Superclass dict get_set_attr: <__main__.GetSetDesc object at 0x02896EB0>
Superclass dict set_attr: <__main__.SetDesc object at 0x02896ED0>
Superclass dict get_attr: <__main__.GetDesc object at 0x02896EF0>
get_set_desc: Set
set_desc: Set
t.attr: 200
get_set_desc: Get
t.get_set_desc: 200
t.set_attr: <__main__.SetDesc object at 0x02896ED0>
t.get_attr: 200
Superclass dict attr: 10
Superclass dict get_set_attr: <__main__.GetSetDesc object at 0x02896EB0>
Superclass dict set_attr: <__main__.SetDesc object at 0x02896ED0>
Superclass dict get_attr: <__main__.GetDesc object at 0x02896EF0>
Instance dict attr: 200
Instance dict get_attr: 200
++Test 4 End++
亲身体验一下描述符。但归根结底,我们在这里看到的是...
先从官方文档定义一下,刷一下记忆:
If an object defines both __get__()
and __set__()
, it is considered a
data descriptor. Descriptors that only define __get__()
are called
non-data descriptors (they are typically used for methods but other
uses are possible).
从输出和失败的片段...
很明显,在重新分配引用描述符(任何类型)的名称之前,按照 MRO 从 class 级别到 superclasses 到的地方像往常一样查找描述符它被定义了。 (参见测试 2,它在实例中定义但未被调用,但使用简单值重新定义。)
现在当名称被重新分配时,事情开始变得有趣了:
如果它是一个数据描述符(有 __set__
),那么真的没有魔法发生,分配给引用描述符的变量的值将传递给描述符的 __set__
并在此方法中使用(关于上面的代码,它被分配给 self.value
)。描述符首先在层次结构 ofc 中查找。顺便说一句,不带 __get__
的描述符本身返回,而不是其 __set__
方法使用的值。
如果它是一个 non-data 描述符(只有 __get__
),那么它被查找了,但是没有 __set__
方法它是 "droped",并且引用它的变量描述符在尽可能低的级别重新分配(实例或子class,取决于我们定义它的位置)。
所以描述符是用来控制、修改、赋值给变量的数据,这些变量就是描述符。所以这是有道理的,如果一个描述符是一个数据描述符,它定义了 __set__
,它可能想要解析你传递的数据,因此在实例字典键分配之前被调用。这就是为什么它首先放在层次结构中的原因。另一方面,如果它是一个只有 __get__
的 non-data 描述符,它可能不关心设置数据,甚至更多——它不能对数据集做任何事情,所以它在分配时从链上掉下来,数据被分配给实例字典键。
此外,新样式 classes 都是关于 MRO(Method Resolution Order)的,因此它会影响每个特征 - 描述符、属性(实际上是描述符也),特殊方法等。描述符基本上是方法,在赋值或属性读取时被调用,所以它是有道理的,它们在 class 级别查找,就像任何其他方法预期的那样。
如果您需要控制赋值,但拒绝对变量进行任何更改,请使用数据描述符,但在其 __set__
方法中引发异常。
我刚刚 运行 进入了 Python 中的描述符,我对“__get__、__set__、[=24 中的描述符协议有了一些想法=]",它在包装方法方面确实做得很好。
但是,在 the protocol 中还有其他规则:
Data and non-data descriptors differ in how overrides are calculated with respect to entries in an instance’s dictionary. If an instance’s dictionary has an entry with the same name as a data descriptor, the data descriptor takes precedence. If an instance’s dictionary has an entry with the same name as a non-data descriptor, the dictionary entry takes precedence.
我没明白,只用 classic 方式查找是不是可以(实例字典 -> class 字典 -> 基础 class字典)?
如果以这种方式实现,数据描述符可以由实例持有,并且描述符本身不必持有 weakrefdict
来持有所有者的不同实例的值。
为什么要将描述符放入查找链中?还有为什么数据描述符放在最开始?
问题是超载之一。假设您有一个 Descriptor
class,并且您将对象的一个属性设置为该对象的一个实例 class:
class Descriptor:
...
def __get__(self, parent, type=None):
return 1
class MyObject:
def __init__(self):
self.foo = Descriptor()
mobj = MyObject()
在这种情况下,您有一个 non-data 描述符。由于 getter.
,任何访问mobj.foo
的代码都将得到结果 1
但是假设您尝试存储那个属性?会发生什么?
答案:一个简单的条目将添加到实例字典中,mobj.foo
将指向存储的任何值。
在这种情况下,如果您随后从 mobj.foo
读取,您会得到哪个值? get函数返回的'1',或者字典中列出的recently-stored"live"值?
对了!在出现冲突的情况下,描述符会悄无声息地消失,剩下的就是检索您存储的内容。
首先 classic 方式(实际上并没有太大变化)不是你描述的那样。实际上,从这个意义上讲,没有基 class,基 classes 只是在 class 创建期间使用的东西。 classic 查找首先在实例中查找,然后在 class.
中查找引入描述符的原因是允许以更简洁的方式自定义属性访问。 classic 方式依赖于可查找的函数来设置和获取属性。新方法还允许使用 @property
装饰器定义属性。
现在我们可以区分数据和 non-data(或 RW 和 RO)描述符。第一个应该注意的是,无论您尝试何种类型的访问(无论是读取、写入还是删除),都进行相同的查找是合理的:
描述符优先于 RO-descriptors 的原因是,如果您有 RO 描述符,您的意图通常是该属性应该是只读的。这意味着在这种情况下使用描述符是正确的。
另一方面,如果您有 RW-descriptor,使用 __dict__
条目存储实际数据会很有用。
还应注意,描述符正确放置在 class 而不是实例中(如果属性查找使用该方法找到对象,则属性查找会自动调用 __get__
)。
为什么不是另一种方式是因为如果将描述符放在实例中,您可能希望该属性实际引用描述符而不是描述符让您认为它是什么(通过调用 __get__
在上面)。例如:
class D:
def __get__(self):
return None
class C:
pass
o = C()
d = D()
o.fubar = d
现在最后的陈述可能是我们实际上将 D()
存储在 o.fubar
中,目的是 o.fubar
到 return d
而不是调用 d.__get__()
会 return None
.
让我们看一个例子:
class GetSetDesc(object):
def __init__(self, value):
self.value=value
def __get__(self, obj, objtype):
print("get_set_desc: Get")
return self.value
def __set__(self, obj, value):
print("get_set_desc: Set")
self.value=value
class SetDesc(object):
def __init__(self, value):
self.value=value
def __set__(self, obj, value):
print("set_desc: Set")
self.value=value
class GetDesc(object):
def __init__(self, value):
self.value=value
def __get__(self, obj, objtype):
print("get_desc: Get")
return self.value
class Test1(object):
attr=10
get_set_attr=10
get_set_attr=GetSetDesc(5)
set_attr=10
set_attr=SetDesc(5)
get_attr=10
get_attr=GetDesc(5)
class Test2(object):
def __init__(self):
self.attr=10
self.get_set_attr=10
self.get_set_attr=GetSetDesc(5)
self.set_attr=10
self.set_attr=SetDesc(5)
self.get_attr=10
self.get_attr=GetDesc(5)
class Test3(Test1):
def __init__(self):
#changing values to see differce with superclass
self.attr=100
self.get_set_attr=100
self.get_set_attr=GetSetDesc(50)
self.set_attr=100
self.set_attr=SetDesc(50)
self.get_attr=100
self.get_attr=GetDesc(50)
class Test4(Test1):
pass
print("++Test 1 Start++")
t=Test1()
print("t.attr:", t.attr)
print("t.get_set_desc:", t.get_set_attr)
print("t.set_attr:", t.set_attr)
print("t.get_attr:", t.get_attr)
print("Class dict attr:", t.__class__.__dict__['attr'])
print("Class dict get_set_attr:", t.__class__.__dict__['get_set_attr'])
print("Class dict set_attr:", t.__class__.__dict__['set_attr'])
print("Class dict get_attr:", t.__class__.__dict__['get_attr'])
#These will obviously fail as instance dict is empty here
#print("Instance dict attr:", t.__dict__['attr'])
#print("Instance dict get_set_attr:", t.__dict__['get_set_attr'])
#print("Instance dict set_attr:", t.__dict__['set_attr'])
#print("Instance dict get_attr:", t.__dict__['get_attr'])
t.attr=20
t.get_set_attr=20
t.set_attr=20
t.get_attr=20
print("t.attr:", t.attr)
print("t.get_set_desc:", t.get_set_attr)
print("t.set_attr:", t.set_attr)
print("t.get_attr:", t.get_attr)
print("Class dict attr:", t.__class__.__dict__['attr'])
print("Class dict get_set_attr:", t.__class__.__dict__['get_set_attr'])
print("Class dict set_attr:", t.__class__.__dict__['set_attr'])
print("Class dict get_attr:", t.__class__.__dict__['get_attr'])
print("Instance dict attr:", t.__dict__['attr'])
#Next two will fail,
#because the descriptor for those variables has __set__
#on the class itself which was called with value 20,
#so the instance is not affected
#print("Instance dict get_set_attr:", t.__dict__['get_set_attr'])
#print("Instance dict set_attr:", t.__dict__['set_attr'])
print("Instance dict get_attr:", t.__dict__['get_attr'])
print("++Test 1 End++")
print("++Test 2 Start++")
t2=Test2()
print("t.attr:", t2.attr)
print("t.get_set_desc:", t2.get_set_attr)
print("t.set_attr:", t2.set_attr)
print("t.get_attr:", t2.get_attr)
#In this test the class is not affected, so these will fail
#print("Class dict attr:", t2.__class__.__dict__['attr'])
#print("Class dict get_set_attr:", t2.__class__.__dict__['get_set_attr'])
#print("Class dict set_attr:", t2.__class__.__dict__['set_attr'])
#print("Class dict get_attr:", t2.__class__.__dict__['get_attr'])
print("Instance dict attr:", t2.__dict__['attr'])
print("Instance dict get_set_attr:", t2.__dict__['get_set_attr'])
print("Instance dict set_attr:", t2.__dict__['set_attr'])
print("Instance dict get_attr:", t2.__dict__['get_attr'])
t2.attr=20
t2.get_set_attr=20
t2.set_attr=20
t2.get_attr=20
print("t.attr:", t2.attr)
print("t.get_set_desc:", t2.get_set_attr)
print("t.set_attr:", t2.set_attr)
print("t.get_attr:", t2.get_attr)
#In this test the class is not affected, so these will fail
#print("Class dict attr:", t2.__class__.__dict__['attr'])
#print("Class dict get_set_attr:", t2.__class__.__dict__['get_set_attr'])
#print("Class dict set_attr:", t2.__class__.__dict__['set_attr'])
#print("Class dict get_attr:", t2.__class__.__dict__['get_attr'])
print("Instance dict attr:", t2.__dict__['attr'])
print("Instance dict get_set_attr:", t2.__dict__['get_set_attr'])
print("Instance dict set_attr:", t2.__dict__['set_attr'])
print("Instance dict get_attr:", t2.__dict__['get_attr'])
print("++Test 2 End++")
print("++Test 3 Start++")
t3=Test3()
print("t.attr:", t3.attr)
print("t.get_set_desc:", t3.get_set_attr)
print("t.set_attr:", t3.set_attr)
print("t.get_attr:", t3.get_attr)
#These fail, because nothing is defined on Test3 class itself, but let's see its super below
#print("Class dict attr:", t3.__class__.__dict__['attr'])
#print("Class dict get_set_attr:", t3.__class__.__dict__['get_set_attr'])
#print("Class dict set_attr:", t3.__class__.__dict__['set_attr'])
#print("Class dict get_attr:", t3.__class__.__dict__['get_attr'])
#Checking superclass
print("Superclass dict attr:", t3.__class__.__bases__[0].__dict__['attr'])
print("Superclass dict get_set_attr:", t3.__class__.__bases__[0].__dict__['get_set_attr'])
print("Superclass dict set_attr:", t3.__class__.__bases__[0].__dict__['set_attr'])
print("Superclass dict get_attr:", t3.__class__.__bases__[0].__dict__['get_attr'])
print("Instance dict attr:", t3.__dict__['attr'])
#Next two with __set__ inside descriptor fail, because
#when the instance was created, the value inside the descriptor in superclass
#was redefined via __set__
#print("Instance dict get_set_attr:", t3.__dict__['get_set_attr'])
#print("Instance dict set_attr:", t3.__dict__['set_attr'])
print("Instance dict get_attr:", t3.__dict__['get_attr'])
#The one above does not fail, because it doesn't have __set__ in
#descriptor in superclass and therefore was redefined on instance
t3.attr=200
t3.get_set_attr=200
t3.set_attr=200
t3.get_attr=200
print("t.attr:", t3.attr)
print("t.get_set_desc:", t3.get_set_attr)
print("t.set_attr:", t3.set_attr)
print("t.get_attr:", t3.get_attr)
#print("Class dict attr:", t3.__class__.__dict__['attr'])
#print("Class dict get_set_attr:", t3.__class__.__dict__['get_set_attr'])
#print("Class dict set_attr:", t3.__class__.__dict__['set_attr'])
#print("Class dict get_attr:", t3.__class__.__dict__['get_attr'])
#Checking superclass
print("Superclass dict attr:", t3.__class__.__bases__[0].__dict__['attr'])
print("Superclass dict get_set_attr:", t3.__class__.__bases__[0].__dict__['get_set_attr'])
print("Superclass dict set_attr:", t3.__class__.__bases__[0].__dict__['set_attr'])
print("Superclass dict get_attr:", t3.__class__.__bases__[0].__dict__['get_attr'])
print("Instance dict attr:", t3.__dict__['attr'])
#Next two fail, they are in superclass, not in instance
#print("Instance dict get_set_attr:", t3.__dict__['get_set_attr'])
#print("Instance dict set_attr:", t3.__dict__['set_attr'])
print("Instance dict get_attr:", t3.__dict__['get_attr'])
#The one above succeds as it was redefined as stated in prior check
print("++Test 3 End++")
print("++Test 4 Start++")
t4=Test4()
print("t.attr:", t4.attr)
print("t.get_set_desc:", t4.get_set_attr)
print("t.set_attr:", t4.set_attr)
print("t.get_attr:", t4.get_attr)
#These again fail, as everything defined in superclass, not the class itself
#print("Class dict attr:", t4.__class__.__dict__['attr'])
#print("Class dict get_set_attr:", t4.__class__.__dict__['get_set_attr'])
#print("Class dict set_attr:", t4.__class__.__dict__['set_attr'])
#print("Class dict get_attr:", t4.__class__.__dict__['get_attr'])
#Checking superclass
print("Superclass dict attr:", t4.__class__.__bases__[0].__dict__['attr'])
print("Superclass dict get_set_attr:", t4.__class__.__bases__[0].__dict__['get_set_attr'])
print("Superclass dict set_attr:", t4.__class__.__bases__[0].__dict__['set_attr'])
print("Superclass dict get_attr:", t4.__class__.__bases__[0].__dict__['get_attr'])
#Again, everything is on superclass, not the instance
#print("Instance dict attr:", t4.__dict__['attr'])
#print("Instance dict get_set_attr:", t4.__dict__['get_set_attr'])
#print("Instance dict set_attr:", t4.__dict__['set_attr'])
#print("Instance dict get_attr:", t4.__dict__['get_attr'])
t4.attr=200
t4.get_set_attr=200
t4.set_attr=200
t4.get_attr=200
print("t.attr:", t4.attr)
print("t.get_set_desc:", t4.get_set_attr)
print("t.set_attr:", t4.set_attr)
print("t.get_attr:", t4.get_attr)
#Class is not affected by those assignments, next four fail
#print("Class dict attr:", t4.__class__.__dict__['attr'])
#print("Class dict get_set_attr:", t4.__class__.__dict__['get_set_attr'])
#print("Class dict set_attr:", t4.__class__.__dict__['set_attr'])
#print("Class dict get_attr:", t4.__class__.__dict__['get_attr'])
#Checking superclass
print("Superclass dict attr:", t4.__class__.__bases__[0].__dict__['attr'])
print("Superclass dict get_set_attr:", t4.__class__.__bases__[0].__dict__['get_set_attr'])
print("Superclass dict set_attr:", t4.__class__.__bases__[0].__dict__['set_attr'])
print("Superclass dict get_attr:", t4.__class__.__bases__[0].__dict__['get_attr'])
#Now, this one we redefined it succeeds
print("Instance dict attr:", t4.__dict__['attr'])
#This one fails it's still on superclass
#print("Instance dict get_set_attr:", t4.__dict__['get_set_attr'])
#Same here - fails, it's on superclass, because it has __set__
#print("Instance dict set_attr:", t4.__dict__['set_attr'])
#This one succeeds, no __set__ to call, so it was redefined on instance
print("Instance dict get_attr:", t4.__dict__['get_attr'])
print("++Test 4 End++")
输出:
++Test 1 Start++
t.attr: 10
get_set_desc: Get
t.get_set_desc: 5
t.set_attr: <__main__.SetDesc object at 0x02896ED0>
get_desc: Get
t.get_attr: 5
Class dict attr: 10
Class dict get_set_attr: <__main__.GetSetDesc object at 0x02896EB0>
Class dict set_attr: <__main__.SetDesc object at 0x02896ED0>
Class dict get_attr: <__main__.GetDesc object at 0x02896EF0>
get_set_desc: Set
set_desc: Set
t.attr: 20
get_set_desc: Get
t.get_set_desc: 20
t.set_attr: <__main__.SetDesc object at 0x02896ED0>
t.get_attr: 20
Class dict attr: 10
Class dict get_set_attr: <__main__.GetSetDesc object at 0x02896EB0>
Class dict set_attr: <__main__.SetDesc object at 0x02896ED0>
Class dict get_attr: <__main__.GetDesc object at 0x02896EF0>
Instance dict attr: 20
Instance dict get_attr: 20
++Test 1 End++
++Test 2 Start++
t.attr: 10
t.get_set_desc: <__main__.GetSetDesc object at 0x028A0350>
t.set_attr: <__main__.SetDesc object at 0x028A0370>
t.get_attr: <__main__.GetDesc object at 0x028A0330>
Instance dict attr: 10
Instance dict get_set_attr: <__main__.GetSetDesc object at 0x028A0350>
Instance dict set_attr: <__main__.SetDesc object at 0x028A0370>
Instance dict get_attr: <__main__.GetDesc object at 0x028A0330>
t.attr: 20
t.get_set_desc: 20
t.set_attr: 20
t.get_attr: 20
Instance dict attr: 20
Instance dict get_set_attr: 20
Instance dict set_attr: 20
Instance dict get_attr: 20
++Test 2 End++
++Test 3 Start++
get_set_desc: Set
get_set_desc: Set
set_desc: Set
set_desc: Set
t.attr: 100
get_set_desc: Get
t.get_set_desc: <__main__.GetSetDesc object at 0x02896FF0>
t.set_attr: <__main__.SetDesc object at 0x02896ED0>
t.get_attr: <__main__.GetDesc object at 0x028A03F0>
Superclass dict attr: 10
Superclass dict get_set_attr: <__main__.GetSetDesc object at 0x02896EB0>
Superclass dict set_attr: <__main__.SetDesc object at 0x02896ED0>
Superclass dict get_attr: <__main__.GetDesc object at 0x02896EF0>
Instance dict attr: 100
Instance dict get_attr: <__main__.GetDesc object at 0x028A03F0>
get_set_desc: Set
set_desc: Set
t.attr: 200
get_set_desc: Get
t.get_set_desc: 200
t.set_attr: <__main__.SetDesc object at 0x02896ED0>
t.get_attr: 200
Superclass dict attr: 10
Superclass dict get_set_attr: <__main__.GetSetDesc object at 0x02896EB0>
Superclass dict set_attr: <__main__.SetDesc object at 0x02896ED0>
Superclass dict get_attr: <__main__.GetDesc object at 0x02896EF0>
Instance dict attr: 200
Instance dict get_attr: 200
++Test 3 End++
++Test 4 Start++
t.attr: 10
get_set_desc: Get
t.get_set_desc: 200
t.set_attr: <__main__.SetDesc object at 0x02896ED0>
get_desc: Get
t.get_attr: 5
Superclass dict attr: 10
Superclass dict get_set_attr: <__main__.GetSetDesc object at 0x02896EB0>
Superclass dict set_attr: <__main__.SetDesc object at 0x02896ED0>
Superclass dict get_attr: <__main__.GetDesc object at 0x02896EF0>
get_set_desc: Set
set_desc: Set
t.attr: 200
get_set_desc: Get
t.get_set_desc: 200
t.set_attr: <__main__.SetDesc object at 0x02896ED0>
t.get_attr: 200
Superclass dict attr: 10
Superclass dict get_set_attr: <__main__.GetSetDesc object at 0x02896EB0>
Superclass dict set_attr: <__main__.SetDesc object at 0x02896ED0>
Superclass dict get_attr: <__main__.GetDesc object at 0x02896EF0>
Instance dict attr: 200
Instance dict get_attr: 200
++Test 4 End++
亲身体验一下描述符。但归根结底,我们在这里看到的是...
先从官方文档定义一下,刷一下记忆:
If an object defines both
__get__()
and__set__()
, it is considered a data descriptor. Descriptors that only define__get__()
are called non-data descriptors (they are typically used for methods but other uses are possible).
从输出和失败的片段...
很明显,在重新分配引用描述符(任何类型)的名称之前,按照 MRO 从 class 级别到 superclasses 到的地方像往常一样查找描述符它被定义了。 (参见测试 2,它在实例中定义但未被调用,但使用简单值重新定义。)
现在当名称被重新分配时,事情开始变得有趣了:
如果它是一个数据描述符(有 __set__
),那么真的没有魔法发生,分配给引用描述符的变量的值将传递给描述符的 __set__
并在此方法中使用(关于上面的代码,它被分配给 self.value
)。描述符首先在层次结构 ofc 中查找。顺便说一句,不带 __get__
的描述符本身返回,而不是其 __set__
方法使用的值。
如果它是一个 non-data 描述符(只有 __get__
),那么它被查找了,但是没有 __set__
方法它是 "droped",并且引用它的变量描述符在尽可能低的级别重新分配(实例或子class,取决于我们定义它的位置)。
所以描述符是用来控制、修改、赋值给变量的数据,这些变量就是描述符。所以这是有道理的,如果一个描述符是一个数据描述符,它定义了 __set__
,它可能想要解析你传递的数据,因此在实例字典键分配之前被调用。这就是为什么它首先放在层次结构中的原因。另一方面,如果它是一个只有 __get__
的 non-data 描述符,它可能不关心设置数据,甚至更多——它不能对数据集做任何事情,所以它在分配时从链上掉下来,数据被分配给实例字典键。
此外,新样式 classes 都是关于 MRO(Method Resolution Order)的,因此它会影响每个特征 - 描述符、属性(实际上是描述符也),特殊方法等。描述符基本上是方法,在赋值或属性读取时被调用,所以它是有道理的,它们在 class 级别查找,就像任何其他方法预期的那样。
如果您需要控制赋值,但拒绝对变量进行任何更改,请使用数据描述符,但在其 __set__
方法中引发异常。