1. Python instance descriptors: when class descriptors aren’t dynamic enough

    Python descriptors are great for customizing access to attributes on a class or instance. They are a big win for tasks like mapping Python objects to data from non-Python sources (such as SQL), since mapped attributes will need to be encoded/decoded and connected to other attributes in some way.

    Below is a very simple descriptor; as you can see, accessing it from both the class and the instance invoke the descriptor protocol:

    class Test(object):
    class Descriptor(object):
        def __get__(self, instance, owner):
            return "Hello, world."
    >>> Test.x = Descriptor()
    >>> Test.x
    'Hello, world.'
    >>> test = Test()
    >>> test.x
    'Hello, world.'

    However, in order to add descriptors to an object, they must be added to the object’s class. Descriptors added to an instance do not invoke the descriptor protocol:

    >>> test.y = Descriptor()
    >>> test.y
    <__main__.Descriptor object at 0x16fe810>

    This means that creating an instance with dynamic (determined at runtime) descriptors requires either the heavy-handed approach of generating a class just for that object (since adding descriptors to its class will add them to all other instances of the class), or the ad-hoc approach of redefining getattr/setattr behavior (essentially re-implementing your own descriptor protocol).

    It turns out the latter approach is not as messy as it first sounds. Below is a class that enables “instance descriptors”:

    class InstanceDescriptorMixin(object):
        def __getattribute__(self, name):
            value = object.__getattribute__(self, name)
            if hasattr(value, '__get__'):
                value = value.__get__(self, self.__class__)
            return value
        def __setattr__(self, name, value):
                obj = object.__getattribute__(self, name)
            except AttributeError:
                if hasattr(obj, '__set__'):
                    return obj.__set__(self, value)
            return object.__setattr__(self, name, value)
    class Test(InstanceDescriptorMixin):
    >>> test = Test()
    >>> test.z = Descriptor()
    >>> test.z
    'Hello, world.'