7

I am reading the explanation of how descriptors work from the link: http://users.rcn.com/python/download/Descriptor.htm#properties.

But, here, under the class Property's __get__ method, I have a doubt regarding the method signature. The method signature is:

def __get__(self, obj, objtype=None):

Here, I know when and how the obj can be None or an actual object.

But, I could not understand: In what cases can the objtype be None? And, how it is useful in practical examples.

1
  • 1
    Perhaps this would benefit from the relevant part of the guide being in the question... to make it clear this asking a different thing from "how do I use __get__"... Commented Sep 30, 2012 at 12:05

5 Answers 5

6

The signature

def __get__(self, obj, objtype=None):

is saying that objtype is an optional argument. If __get__ is called with only one argument, then objtype will be set to None.


For example, Foo could steal a method from Bar by defining foo.baz this way:

class Foo(object):
    pass
class Bar(object):
    def baz(self):
        print('Hi')        

foo = Foo()
foo.baz = Bar.baz.__get__(foo)
print(foo.__dict__)
# {'baz': <bound method ?.baz of <__main__.Foo object at 0xb787006c>>}
foo.baz()
# Hi

If instead, the 2-argument form of __get__ had been used,

foo.baz = Bar.baz.__get__(foo, foo.__class__)

then foo.baz is the unbound method Bar.baz and foo.baz() raises

TypeError: unbound method baz() must be called with Bar instance as first argument (got nothing instead)

Note that in Python3 the concept of unbound method has been removed. There is no more checking to see that the calling obj's class is of the right type. So in Python3, both the 1-argument and 2-argument form for defining foo.baz works.

Sign up to request clarification or add additional context in comments.

7 Comments

That's not answering the question. Of course, you could call it manually, but that's beside the point.
@unutbu: Can you give any example where objtype can be None when get is called using Property ?
@phant0m: I think you can remove your comment now because unutbu has edited his answer with the required explanation and example as I wanted to know.
@GodMan I disagree, methods are not properties.
@phant0m: properties are methods. Methods are descriptors. I think the question has more to do with how __get__ works with descriptors in general than with properties in specific.
|
2

Perhaps I am rewording an above answer, but this presentation of the thoughts above seemed easier to follow for me.

Consider this implementation of @cached_property

class cached_property(object):
    """Like @property, but caches the value."""

    def __init__(self, func):
        self._func = func

    def __get__(self, obj, cls):
        if obj is None:
            return self
        value = self._func(obj)
        obj.__dict__[self.__name__] = value
        return value

I had the same question regarding "Why is obj checked for None?" and "Why return self?"

Here's an example of how both of the situations arise

The typical usage:

class Foo(object):
    @cached_property
    def foo(self):
        # Would usually have significant computation here
        return 9001

foo_instance = Foo()
foo_foo = foo_instance.foo # Behind the scenes invokes Foo.foo.__get__(foo_instance, Foo)

Wait, yeah this is what I expect, what about the case when obj is None?

The not so typical usage (grabbing access to an unbound version of the property)

(Taking the same Foo as above)

>> Foo.foo
<__main__.cached_property at 0x178bed0>

In this case the call looks like Foo.foo.__get__(None, Foo)

Comments

1

The python docs discuss this well under Implementing Descriptors. The signature is actually what is provided below. As you mentioned instance maybe None but owner should never be None. Maybe there was a mistake in the how to you were reading.

object.__get__(self, instance, owner)

Comments

0

objtype represents the "owner" class of obj, meaning you pass an instance to obj and its base class to objtype. However, that means that obj can be None, as there might be no instance of the given class, but objtype can not, as that would raise an AttributeError exception. As Marwan said, you sure you didn't mix something up there? ;)

1 Comment

I clearly mentioned that I know how obj works as mentioned in docs.python.org/reference/…. But, I was just clarifying why objtype=None is written in the URL users.rcn.com/python/download/Descriptor.htm#properties where __get__ is defined
0

re-edited from https://github.com/youngsterxyf/mpdp-code/blob/master/chapter9/lazy.py

    # lazy.py
    class LazyProperty:

        def __init__(self, method):
            self.method = method
            self.method_name = method.__name__
            #print('function overriden: {}'.format(self.method))
            #print("function's name: {}".format(self.method_name))

        def __get__(self, obj, cls):
            if not obj:
               return None
            value = self.method(obj)
            #print('value {}'.format(value))
            setattr(obj, self.method_name, value)
            return value


    class Test:

        def __init__(self):
            self.x = 'foo'
            self.y = 'bar'
            self._resource = None

        @LazyProperty
        def resource(self):
            print('initializing self._resource which is: {}'.format(self._resource))
            self._resource = tuple(range(5))
            return self._resource

    def main_Py27():
        # python 2.7.12
        t = Test()
        print(t.x)
        #>>> foo
        print(t.y)
        #>>> bar
        print(t.resource)
        #>>> <__main__.LazyProperty instance at 0x02C2E058>
        print(t.resource.__get__(t,Test))
        #>>> initializing self._resource which is: None
        #>>> (0, 1, 2, 3, 4)
        print(t.resource)
        #>>> (0, 1, 2, 3, 4)

    def main_Py3():
        # python 3.x
        t = Test()
        print(t.x)
        #>>> foo
        print(t.y)
        #>>> bar
        print(t.resource)
        #>>> initializing self._resource which is: None
        #>>> (0, 1, 2, 3, 4)
        print(t.resource)
        #>>> (0, 1, 2, 3, 4)

    def main():
        import sys
        if sys.version_info < (3,0):
            main_Py27()
        else:
            main_Py3()

    if __name__ == '__main__':
        main()

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.