classification
Title: Descriptor example in documentation is confusing, possibly wrong
Type: Stage:
Components: Documentation Versions:
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Benjamin Wohlwend, docs@python, r.david.murray, rhettinger
Priority: normal Keywords:

Created on 2017-09-13 08:38 by Benjamin Wohlwend, last changed 2017-09-14 06:25 by rhettinger.

Messages (3)
msg302033 - (view) Author: Benjamin Wohlwend (Benjamin Wohlwend) Date: 2017-09-13 08:38
The first descriptor example in the descriptor docs (https://docs.python.org/3/howto/descriptor.html#descriptor-example) stores the value on the descriptor instance, which is shared among all MyClass instances. This leads to surprising (and arguably buggy from a user perspective) behaviour:

    m1, m2 = MyClass(), MyClass()
    m1.x = 5
    m2.x = 10
    print(m1.x, m2.x)
    >>> 10 10

I'm not sure how this could be fixed without making the example much more complicated (e.g. by introducing a "values" weakref dictionary on the descriptor instance). 

Maybe pointing this behaviour out in the docs could be enough, although I don't see any useful use case for a class that has this behaviour.
msg302072 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2017-09-13 13:09
Here is a not-much-more-complicated version that solves the problem.  It is probably worth changing as the revised example makes clear the difference between self and obj, which is an important distinction.

class RevealAccess(object):                                                                                                                                                                                                                                                                                                    
    """A data descriptor that sets and returns values                                                                                                                                                                                                                                                                          
       normally and prints a message logging their access.                                                                                                                                                                                                                                                                     
    """                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                               
    def __init__(self, initval=None, name='var'):                                                                                                                                                                                                                                                                              
        self.attrname = '_' + str(random.random())[2:]                                                                                                                                                                                                                                                                                 
        self.name = name                                                                                                                                                                                                                                                                                                       
        self.initval = initval                                                                                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                                                                                               
    def __get__(self, obj, objtype):                                                                                                                                                                                                                                                                                           
        print('Retrieving', self.name)                                                                                                                                                                                                                                                                                         
        return getattr(obj, self.attrname, self.initval)                                                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                                                                               
    def __set__(self, obj, val):                                                                                                                                                                                                                                                                                               
        print('Updating', self.name)                                                                                                                                                                                                                                                                                           
        setattr(obj, self.attrname, val)                                                                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                                                                               
                                                                                                                                                                                                                                                                                                                               
class MyClass:                                                                                                                                                                                                                                                                                                                 
    x = RevealAccess(10, 'var "x"')                                                                                                                                                                                                                                                                                            
    y = 5
msg302142 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2017-09-14 06:25
The example should be modernized to show-off the new __set_name__() part of the descriptor protocol.  Computing the name on-demand is better that relying an optional user entered alternate name and having to store that name for future reference.
History
Date User Action Args
2017-09-14 06:25:59rhettingersetassignee: docs@python ->
messages: + msg302142
2017-09-13 13:09:13r.david.murraysetnosy: + rhettinger, r.david.murray
messages: + msg302072
2017-09-13 08:38:27Benjamin Wohlwendcreate