Neat way to get descriptor object
Asked Answered
S

2

12

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

Selfinterest answered 7/2, 2014 at 13:46 Comment(1)
How about type(self).__dict__['attr'] ?Tarpan
H
22

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.

Hunter answered 7/2, 2014 at 14:8 Comment(6)
This will get instance attributes, if they're different from the class' attributes, right? Otherwise I prefer getattr over other approaches.Entrance
Please elaborate. I do not understand what situation you are contemplating.Hunter
a = A(); a.attr = = 5; getattr(type(a), 'attr', None). Won't that be 5 and not 4?Entrance
@2rs2ts: No it would not. It still returns the descriptor. Try it! :)Hunter
Oh, I'd have to do getattr(a, 'attr', None) for what I'm talking about, yeah?Entrance
@2rs2ts: Yes. getattr(a, 'attr') suffices, since None is the default. Moreover, a.attr would still access the descriptor ahead of the instance __dict__ if the descriptor is a data descriptor (i.e. also defines __set__). See Python attributes and methods.Hunter
G
11

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.

Greathouse answered 7/2, 2014 at 13:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.