[Python-ideas] descriptors outside of classes

Raymond Hettinger raymond.hettinger at gmail.com
Wed Mar 30 22:26:15 CEST 2011

On Mar 29, 2011, at 10:59 AM, Eric Snow wrote:

> Here's another outlandish idea.  How about if descriptors could be used outside of classes.  I.e. any global or local variable could be assigned a descriptor object and the descriptor protocol would be respected for that variable.  This would be a pretty messy change, and I have no illusions that the idea will go anywhere.  However, would there be room for this in python?

FWIW, you can already do this with locals (am not saying you should do it, am just saying that you can do it).

Remember, the essential mechanism for descriptors is in the lookup function, not in the descriptor itself.  For example, property() objects are descriptors only because they define one of the descriptor protocol methods (__get__, et al).  Whether it gets invoked solely depends on how you look it up.   If you use regular dictionary lookup, a.__class__.__dict__['x'], then the property object is retrieved but no special action occurs.  If you use dotted lookup, a.x, then the property's __get__ method is called.  This is because the lookup function, object.__getattribute__(), has code to detect and invoke descriptors.  

A ultra-simplified version of the lookup functions's psuedo-code looks like this:
          value = kls.__dict__[key]
          if hasattr(value, '__get__'):
                return call_the_getter(kls ,key)
                return the value

Knowing this, it is possible to emulate that behavior with a dictionary whose lookup function, __getitem__(), can detect and invoke some sort of descriptor protocol.

Since eval/exec can use arbitrary mappings for locals, you can use your custom dictionary while executing arbitrary python code.  Essentially, you're executing python code in an environment where the lookup function for locals has been trained to handle your custom descriptor protocol.


----- simple example -----

class MyDict:
    def __init__(self, mapping):
        self.mapping = mapping
    def __getitem__(self, key):
        value = self.mapping[key]
        if hasattr(value, '__get__'):
            print('Invoking descriptor on', key)
            return value.__get__(key)
        print('Getting', key)
        return value
    def __setitem__(self, key, value):
        self.mapping[key] = value

class Property:
    def __init__(self, getter):
        self.getter = getter
    def __get__(self, key):
        return self.getter(key)

if __name__ == '__main__':   
    md = MyDict({})
    md['x'] = 10
    md['_y'] = 20
    md['y'] = Property(lambda key: md['_'+key])
    print(eval('x+y+1', {}, md))

More information about the Python-ideas mailing list