Antigluk Antigluk - 1 month ago 14
Python Question

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

Answer

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.