Search code examples
pythonpython-3.xdescriptorpython-descriptors

Neat way to get descriptor object


In Python 3

class A(object):
    attr = SomeDescriptor()
    ...
    def somewhere(self):
        # need to check is type of self.attr is SomeDescriptor()
        desc = self.__class__.__dict__[attr_name]
        return isinstance(desc, SomeDescriptor)

Is there better way to do it? I don't like this self.__class__.__dict__ stuff


Solution

  • A.attr causes Python to call SomeDescriptor().__get__(None, A) so if you have SomeDescriptor.__get__ return self when inst is None, then A.attr will return the descriptor:

    class SomeDescriptor():
        def __get__(self, inst, instcls):
            if inst is None:
                # instance attribute accessed on class, return self
                return self
    

    Then you access the descriptor with

    desc  = type(self).attr
    

    If the attribute's name is known only as a string, attr_name, then you would use

    desc  = getattr(type(self), attr_name)
    

    This works even if self is a instance of a subclass of A, whereas

    desc = self.__class__.__dict__[attr_name]
    

    would only work if self is an instance of A.


    class SomeDescriptor():
        def __get__(self, inst, instcls):
            if inst is None:
                # instance attribute accessed on class, return self
                return self
            return 4
    
    class A():
        attr = SomeDescriptor()
        def somewhere(self):
            attr_name = 'attr'
            desc  = getattr(type(self), attr_name)
            # desc = self.__class__.__dict__[attr_name]  # b.somewhere() would raise KeyError
            return isinstance(desc, SomeDescriptor)
    

    This shows A.attr returns the descriptor, and a.somewhere() works as expected:

    a = A()
    print(A.attr)
    # <__main__.SomeDescriptor object at 0xb7395fcc>    
    print(a.attr)
    # 4    
    print(a.somewhere())
    # True
    

    This shows it works for subclasses of A too. If you uncomment desc = self.__class__.__dict__[attr_name], you'll see b.somewhere() raises a KeyError:

    class B(A): pass
    b = B()
    print(B.attr)
    # <__main__.SomeDescriptor object at 0xb7395fcc>   
    print(b.attr)
    # 4    
    print(b.somewhere())
    # True
    

    By the way, even if you do not have full control over the definition of SomeDescriptor, you can still wrap it in a descriptor which returns self when inst is None:

    def wrapper(Desc):
        class Wrapper(Desc):
            def __get__(self, inst, instcls):
                if inst is None: return self
                return super().__get__(inst, instcls)
        return Wrapper
    
    class A():
        attr = wrapper(SomeDescriptor)()
        def somewhere(self):
            desc  = type(self).attr
            # desc = self.__class__.__dict__[attr_name]  # b.somewhere() would raise KeyError
            return isinstance(desc, SomeDescriptor)
    

    So there is no need to use

    desc = self.__class__.__dict__[attr_name]
    

    or

    desc = vars(type(self))['attr']
    

    which suffers from the same problem.