Аккуратный способ получить объект дескриптора

В питоне 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)

Есть ли лучший способ сделать это? Мне не нравится это self.__class__.__dict__ материал

2 ответа

Решение

A.attr вызывает Python для вызова SomeDescriptor().__get__(None, A) так что если у вас есть SomeDescriptor.__get__ вернуть self когда inst является None, затем A.attr вернет дескриптор:

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

Затем вы получаете доступ к дескриптору с

desc  = type(self).attr

Если имя атрибута известно только как строка, attr_nameтогда вы бы использовали

desc  = getattr(type(self), attr_name)

Это работает, даже если self является экземпляром подкласса A, в то время как

desc = self.__class__.__dict__[attr_name]

будет работать только если self это пример 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)

Это показывает A.attr возвращает дескриптор, и a.somewhere() работает как положено:

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

Это показывает, что это работает для подклассов A тоже. Если вы раскомментируетеdesc = self.__class__.__dict__[attr_name], вот увидишь b.somewhere() вызывает ключевую ошибку:

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

Кстати, даже если у вас нет полного контроля над определением SomeDescriptor, вы все равно можете обернуть его в дескриптор, который возвращает self когда inst Нет

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)

Таким образом, нет необходимости использовать

desc = self.__class__.__dict__[attr_name]

или же

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

которая страдает от той же проблемы.

Да, чтобы получить доступ к дескрипторам в классе, единственный способ предотвратить descriptor.__get__ быть призванным пройти через класс __dict__,

Вы могли бы использовать type(self) чтобы получить доступ к текущему классу, и vars() чтобы получить доступ к __dict__ более API-совместимыми способами:

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

Если ваш дескриптор полностью пользовательский, вы всегда можете вернуть self от SomeDescriptor.__get__() если аргумент экземпляра None, что происходит, когда вы обращаетесь к дескриптору непосредственно в классе. Вы можете бросить vars() позвоните и идите прямо к:

desc = type(self).attr

property() объект дескриптор делает именно это.

Другие вопросы по тегам