Neat way to get descriptor object

人走茶凉 提交于 2019-12-04 03:37:59

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.

Yes, to access descriptors on a class, the only way to prevent descriptor.__get__ from being invoked is to go through the class __dict__.

You could use type(self) to access the current class, and vars() to access the __dict__ in more API-compliant ways:

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

If your descriptor is entirely custom, you can always return self from SomeDescriptor.__get__() if the instance argument is None, which happens when you access the descriptor directly on the class. You can drop the vars() call and go straight to:

desc = type(self).attr

The property() descriptor object does exactly this.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!