descriptor - Why attribute lookup in Python is designed this way (precedence chain)? -


i've ran descriptors in python, , got ideas descriptor protocol on "__get__, __set__, __delete__", , did great job on wrapping methods.

however, in the protocol, there're other rules:

data , non-data descriptors differ in how overrides calculated respect entries in instance’s dictionary. if instance’s dictionary has entry same name data descriptor, data descriptor takes precedence. if instance’s dictionary has entry same name non-data descriptor, dictionary entry takes precedence.

i don't point, isn't ok in classic way(instance dictionary -> class dictionary -> base class dictionary)?
, if implement way, data descriptors can hold instances, , descriptor not have hold weakrefdict hold values different instances of owner.
why put descriptors lookup chain? , why data descriptor placed in beginning?

let's see on example:

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 see differce 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 fail instance dict 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 2 fail, #because descriptor variables has __set__ #on class called value 20, #so instance 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 test class not affected, these 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 test class not affected, these 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 defined on test3 class itself, let's see 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 2 __set__ inside descriptor fail, because #when instance created, value inside 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 1 above not fail, because doesn't have __set__ in #descriptor in superclass , therefore 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 2 fail, 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 1 above succeds redefined 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, defined in superclass, not class #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, on superclass, not 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 not affected assignments, next 4 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, 1 redefined succeeds print("instance dict attr:", t4.__dict__['attr']) #this 1 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 has __set__ #print("instance dict set_attr:", t4.__dict__['set_attr']) #this 1 succeeds, no __set__ call, redefined on instance print("instance dict get_attr:", t4.__dict__['get_attr'])  print("++test 4 end++") 

the output:

++test 1 start++ t.attr: 10 get_set_desc: t.get_set_desc: 5 t.set_attr: <__main__.setdesc object @ 0x02896ed0> get_desc: t.get_attr: 5 class dict attr: 10 class dict get_set_attr: <__main__.getsetdesc object @ 0x02896eb0> class dict set_attr: <__main__.setdesc object @ 0x02896ed0> class dict get_attr: <__main__.getdesc object @ 0x02896ef0> get_set_desc: set set_desc: set t.attr: 20 get_set_desc: t.get_set_desc: 20 t.set_attr: <__main__.setdesc object @ 0x02896ed0> t.get_attr: 20 class dict attr: 10 class dict get_set_attr: <__main__.getsetdesc object @ 0x02896eb0> class dict set_attr: <__main__.setdesc object @ 0x02896ed0> class dict get_attr: <__main__.getdesc object @ 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 @ 0x028a0350> t.set_attr: <__main__.setdesc object @ 0x028a0370> t.get_attr: <__main__.getdesc object @ 0x028a0330> instance dict attr: 10 instance dict get_set_attr: <__main__.getsetdesc object @ 0x028a0350> instance dict set_attr: <__main__.setdesc object @ 0x028a0370> instance dict get_attr: <__main__.getdesc object @ 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: t.get_set_desc: <__main__.getsetdesc object @ 0x02896ff0> t.set_attr: <__main__.setdesc object @ 0x02896ed0> t.get_attr: <__main__.getdesc object @ 0x028a03f0> superclass dict attr: 10 superclass dict get_set_attr: <__main__.getsetdesc object @ 0x02896eb0> superclass dict set_attr: <__main__.setdesc object @ 0x02896ed0> superclass dict get_attr: <__main__.getdesc object @ 0x02896ef0> instance dict attr: 100 instance dict get_attr: <__main__.getdesc object @ 0x028a03f0> get_set_desc: set set_desc: set t.attr: 200 get_set_desc: t.get_set_desc: 200 t.set_attr: <__main__.setdesc object @ 0x02896ed0> t.get_attr: 200 superclass dict attr: 10 superclass dict get_set_attr: <__main__.getsetdesc object @ 0x02896eb0> superclass dict set_attr: <__main__.setdesc object @ 0x02896ed0> superclass dict get_attr: <__main__.getdesc object @ 0x02896ef0> instance dict attr: 200 instance dict get_attr: 200 ++test 3 end++ ++test 4 start++ t.attr: 10 get_set_desc: t.get_set_desc: 200 t.set_attr: <__main__.setdesc object @ 0x02896ed0> get_desc: t.get_attr: 5 superclass dict attr: 10 superclass dict get_set_attr: <__main__.getsetdesc object @ 0x02896eb0> superclass dict set_attr: <__main__.setdesc object @ 0x02896ed0> superclass dict get_attr: <__main__.getdesc object @ 0x02896ef0> get_set_desc: set set_desc: set t.attr: 200 get_set_desc: t.get_set_desc: 200 t.set_attr: <__main__.setdesc object @ 0x02896ed0> t.get_attr: 200 superclass dict attr: 10 superclass dict get_set_attr: <__main__.getsetdesc object @ 0x02896eb0> superclass dict set_attr: <__main__.setdesc object @ 0x02896ed0> superclass dict get_attr: <__main__.getdesc object @ 0x02896ef0> instance dict attr: 200 instance dict get_attr: 200 ++test 4 end++ 

try taste on descriptors. bottomline, see here is...

first, definition official docs refresh memory:

if object defines both __get__() , __set__(), considered data descriptor. descriptors define __get__() called non-data descriptors (they typically used methods other uses possible).

from output , failed snippets...

it's clear before name referencing descriptor(any type) reassigned, descriptor looked usual following mro class level superclasses place defined. (see test 2, it's defined in instance , doesn't called, gets redefined simple value.)

now when name reassigned, things start interesting:

if it's data descriptor (has __set__), no magic happens , value assigned variable referencing descriptor passes descriptors's __set__ , used inside method (regarding code above it's assigned self.value). descriptor first looked in hierarchy ofc. btw, descriptor without __get__ returned itself, not value used __set__ method.

if it's non-data descriptor (has __get__), it's looked up, having no __set__ method it's "droped", , variable referencing descriptor gets reassigned @ lowest possible level (instance or subclass, depending on define it).

so descriptors used control, modify, data assigned variables, made descriptors. makes sence, if descriptor data descriptor, defines __set__, wants parse data pass , hence gets called prior instance dictionary key assignment. that's why it's put in hierarchy @ first place. on other hand, if it's non-data descriptor __get__, doesn't care setting data, , more - can't data beign set, falls off chain on assignment , data gets assigned instance dictionary key.

also, new style classes mro (method resolution order), affects every feature - descriptor, properties (which in fact descriptors too), special methods, etc. descriptors basicly methods, called on assignment or attribute read, makes sence, looked @ class level other method expected to.

if need control assignment, refuse change variable use data descriptor, raise , exception in __set__ method.


Comments

Popular posts from this blog

php - Wordpress website dashboard page or post editor content is not showing but front end data is showing properly -

How to get the ip address of VM and use it to configure SSH connection dynamically in Ansible -

javascript - Get parameter of GET request -