Fire Crow
Fire Crow

Reputation: 7729

python descriptors sharing values across classes

A python descriptor that I'm working with is sharing its value across all instances of its owner class. How can I make each instance's descriptor contain its own internal values?

class Desc(object):
    def __init__(self, initval=None,name='val'):
        self.val = initval
        self.name = name

    def __get__(self,obj,objtype):
        return self.val

    def __set__(self,obj,val):
        self.val = val

    def __delete__(self,obj):
        pass


class MyClass(object):
    desc = Desc(10,'varx')

if __name__ == "__main__":
    c = MyClass()
    c.desc = 'max'
    d = MyClass()
    d.desc = 'sally'

    print(c.desc)
    print(d.desc)

The output is this, the last call set the value for both objects:

localhost $ python descriptor_testing.py 
sally
sally

Upvotes: 3

Views: 967

Answers (1)

Matt Anderson
Matt Anderson

Reputation: 19769

There is only one descriptor object, stored on the class object, so self is always the same. If you want to store data per-object and access it through the descriptor, you either have to store the data on each object (probably the better idea) or in some data-structure keyed by each object (an idea I don't like as much).

I would save data on the instance object:

class Desc(object):
    default_value = 10
    def __init__(self, name):
        self.name = name

    def __get__(self,obj,objtype):
        return obj.__dict__.get(self.name, self.default_value)
        # alternatively the following; but won't work with shadowing:
        #return getattr(obj, self.name, self.default_value)

    def __set__(self,obj,val):
        obj.__dict__[self.name] = val
        # alternatively the following; but won't work with shadowing:
        #setattr(obj, self.name, val)

    def __delete__(self,obj):
        pass


class MyClass(object):
    desc = Desc('varx')

In this case, the data will be stored in the obj's 'varx' entry in its __dict__. Because of how data descriptor lookup works though, you can "shadow" the storage location with the descriptor:

class MyClass(object):
    varx = Desc('varx')

In this case, when you do the lookup:

MyClass().varx

The descriptor object gets called and can do its lookup, but when the lookup goes like this:

MyClass().__dict__['varx']

The value is returned directly. Thus the descriptor is able to store its data in a 'hidden' place, so to speak.

Upvotes: 7

Related Questions