为什么self.__dict__可以触发getattribute而instance.attribute不能?

huangapple go评论61阅读模式
英文:

Why self.__dict__ can trigger getattribute and instance.attribute can't?

问题

代码示例表明,在数据描述符中,有一些不同的表现和行为,这可能是因为Python解释器的内部实现细节。以下是对您提出的谜题的一些可能的解释:

Puzzle1:
super().__setattr__(attr, value)object.__setattr__(self, attr, value) 这两个表达式被解释为设置属性值的操作,因此它们不会触发__get____getattribute__。这是因为它们被视为直接的属性赋值,而不是通过__getattribute__机制访问属性。这是Python解释器的内部行为,不会触发getset方法。

Puzzle2:
self.__dict__[attr] = value 中,self.__dict__ 是一个字典,Python解释器解析这一部分时会触发__getattribute__,因为它需要访问__dict__属性。然而,在 self.name = name 中,self.name 被视为直接的属性分配,不会触发__getattribute__。这是因为在Python中,属性分配和属性访问是不同的操作,它们在解释器中的处理方式也不同。

Puzzle3:
self.__setattr__('name', value) 中,您正在调用set_name方法,这个方法在内部执行了self.__setattr__('name', value),这导致了递归的调用。这是为什么您看到多次 "in set" 输出的原因。在这个情况下,set_name 方法内部的self.__setattr__('name', value) 不会触发__getattribute__,因为它只是调用了属性的设置操作,而不是属性的获取操作。

总之,Python的属性访问和赋值操作在内部有不同的实现机制,这些机制可能会导致不同的行为。这些谜题的解释可能涉及Python解释器的内部细节和优化策略。如果需要深入了解这些细节,可能需要查阅Python解释器的源代码或更深入的文档。

英文:

The proof verify that self.__dict__ can trigger __getattribute__ in descriptor.

class Room:
    def __init__(self,name):
        self.name = name
    def __getattribute__(self,attr):
        print('in __getattribute__',attr)
        return  object.__getattribute__(self,attr)
    def __setattr__(self,attr,value):
        print('in __setattr__',attr)
        self.__dict__[attr] = value
        print('over')

Create an instance,i add some comments starting with # in the output info:

x = Room('r1')
# self.name = name in __init__ trigger __setattr__
in __setattr__ name  
# python interpreter read part of self.__dict__[attr] = value,extract self.__dict__ to trigger __getattribute__.
in __getattribute__ __dict__  
over

We saw that python interpreter can extract part of string in a whole line to trigger __getattribute__ method.

class Sample:
    def __init__(self, name):
        self.name = name
    def __get__(self,instance,owner):
        print('get called')
    def __set__(self, instance, value):
        print('set called')

class Person:
    name = Sample('name')

Make an instance:

p = Person()
p.name
get called

p.name can trigger __get__

p.name = 'tom'
set called

For the command p.name = 'tom',why python interpreter do not extract p.name invoke __get__ and then make p.name = 'tom' invoke __set__? Why the output is not as below?

p.name = 'tom'
get called   # p.name  trigger
set called   # p.name = 'tom' trigger

@jsbueno,The default __setattr__ will, ordinarily, create an entry in the instance __dict__,
but that one will be refereed to in native code, using the internal slots, and not be redirected through __gettattribute__.It is not true.

import time
class Room:
    def __init__(self,name):
        self.name = name
    def __getattribute__(self,attr):
        print('in __getattribute__',attr)
        return  object.__getattribute__(self,attr)
    def __setattr__(self,attr,value):
        print('in __setattr__',attr)
        time.sleep(3)
        self.__setattr__(attr, value)
        print('over')

If we write self.__setattr__(attr, value) instead of super.__setattr__(attr, value),python interpreter extract self.__setattr__ and redirect into __gettattribute__,then
parse super.__setattr__(attr, value) in the whole as __setattr__ ,jump into __setattr__ again,repeat without endness until you stop it by ctl+c.

x = Room('r1')
in __setattr__ name
in __getattribute__ __setattr__
in __setattr__ name
in __getattribute__ __setattr__
in __setattr__ name
in __getattribute__ __setattr__
in __setattr__ name
in __getattribute__ __setattr__
in __setattr__ name
in __getattribute__ __setattr__
in __setattr__ name

We draw some conclusion on data descriptor from above codes.
1.expressions without triggering __getattribute__.

super().__setattr__(attr, value)  
object.__setattr__(self,attr, value)  # i have checked

2.expressions with triggering __getattribute__.

self.__setattr__(attr, value)
self.__dict__[attr] = value

When self.__setattr__ trigger __getattribute__,__setattr__ was parsed as attr.
When self.__dict__ trigger __getattribute__,__dict__ was parsed as attr.
Puzzles remain here.
Puzzle1:
With same structure:

super().__setattr__(attr, value)  
object.__setattr__(self,attr, value)
self.__setattr__(attr, value)

,python interpreter parse the first two expressions as setting value for attribute,never triger get nor set,Why python interpreter read part of code snippet self.__setattr__ in self.__setattr__(attr, value),to trigger get (__getattribute__) in a hurry?

Puzzle2:
self.__dict__[attr] = value is an assignment in the whole,python interpreter extract part code snippet self.__dict__ ,to trigger get,why don't python extract self.name in self.name = name in __init__ to trigger get (__getattribute__) ,with same action ?

Puzzle3:
Dig more deeper on property management,more strange action shocked me.As the above codes show that self.__setattr__(attr, value) trigger __getattribute__ in data descriptor,it trigger set instead of get in below code:

import time
class Room:
     def __init__(self,value):
         self.name = value  
     def get_name(self):
         print('in get')
         return self.__dict__['name']       
     def set_name(self, value):
         print('in set')
         time.sleep(3)
         self.__setattr__('name', value)
     name = property(get_name, set_name) 
r = Room('r1')
in set
in set
in set
in set

So i feel that some rules or logic still do not be summarized.

答案1

得分: 2

Assignemts will trigger __setattr__ for ordinary objects, and call the __set__ method in descriptors, as you verified.

The only reason your first example runs __getattribute__ to retrieve the instance __dict__ is because your custom implementation of __setattr__ does so.

The default __setattr__ will, ordinarily, create an entry in the instance __dict__, but that one will be refereed to in native code, using the internal slots, and not be redirected through __gettattribute__.

You can verify this easily by using the default __setattr__ implementation by callign super(), instead of creating a dict entry yourself:

class Room:
    def __init__(self, name):
        self.name = name
    def __getattribute__(self, key):
        print('in __getattribute__', key)
        return  object.__getattribute__(self, key)
    def __setattr__(self, attr, value):
        print('in __setattr__', attr)
        super().__setattr__(attr, value)
        print('over')

And in the interactive interpreter:

In [35]: r = Room("blah")
in __setattr__ name
over
In [36]: r.name
in __getattribute__ name
Out[36]: 'blah'

In [37]: r.__dict__
in __getattribute__ __dict__
Out[37]: {'name': 'blah'}

So, the behavior you observed in your __set__ in the descriptor is the ordinary behavior.

英文:

Assignemts will trigger __setattr__ for ordinary objects, and call the __set__ method in descriptors, as you verified.

The only reason your first example runs __getattribute__ to retrieve the instance __dict__ is because your custom implementation of __setattr__ does so.

The default __setattr__ will, ordinarily, create an entry in the instance __dict__, but that one will be refereed to in native code, using the internal slots, and not be redirected through __gettattribute__.

You can verify this easily by using the default __setattr__ implementation by callign super(), instead of creating a dict entry yourself:

class Room:
    def __init__(self,name):
        self.name = name
    def __getattribute__(self,key):
        print('in __getattribute__',key)
        return  object.__getattribute__(self,key)
    def __setattr__(self,attr,value):
        print('in __setattr__',attr)
        super().__setattr__(attr, value)
        print('over')

And in the interactive interpreter:

In [35]: r = Room("blah")
in __setattr__ name
over
In [36]: r.name
in __getattribute__ name
Out[36]: 'blah'

In [37]: r.__dict__
in __getattribute__ __dict__
Out[37]: {'name': 'blah'}

So, the behavior you observed in your __set__ in the descriptor is the ordinary behavior.

huangapple
  • 本文由 发表于 2023年8月4日 22:51:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76837017.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定