12

See the simple example below:

class Celsius(object):
    def __init__(self, value=0.0):
        self.value = float(value)
    def __get__(self, instance, owner): 
         return self.value 
    def __set__(self, instance, value):
         self.value = float(value)
    def __call__(self):
         print('__call__ called')

class Temperature(object):
    celsius = Celsius()
    def __init__(self):
       self.celsius1 = Celsius()


T = Temperature()
print('T.celsius:', T.celsius)
print('T.celsius1:', T.celsius1)

output
T.celsius: 0.0
T.celsius1: <__main__.Celsius object at 0x023544F0>

I wonder why they have different output. I know T.celsius will call the __get__ and T.celsius1 call the __call__.

Rafał Rawicki
  • 22,324
  • 5
  • 59
  • 79
codest
  • 131
  • 1
  • 5

5 Answers5

8

The differences lie in the fact the the first attribute is a class attribute while the second is an instance attribute.

As per the documentation, If an object that implements at least the first of the Descriptor methods (__get__, __set__ and __delete__) is held in a class attribute, its __get__ method will be called when accessed. This is not the case with an instance attribute. You can learn more from the howto.

The __call__ method of an object only comes into play when the object is invoked like a function:

>>> class Foo:
...    def __call__(self):
...        return "Hello there!"
...
>>> f = Foo()
>>> f() 
'Hello There!'
jsbueno
  • 99,910
  • 10
  • 151
  • 209
brice
  • 24,329
  • 7
  • 79
  • 95
  • Please write when (and why) invoke object as a function. – IProblemFactory Mar 28 '12 at 12:35
  • @ProblemFactory [Advanced decorator usage](http://wiki.python.org/moin/PythonDecorators), for example. Passing an object into a `map`, `filter` or `reduce` function for advanced capabilities. Smart dynamic dispatch. Code instrumentation. Monkey patching. The list goes on, and on, and on... – brice Mar 28 '12 at 12:38
  • Also on and on goes the inconsistency, complexity and ugliness of the Python language – Madis Nõmme May 16 '20 at 09:36
3

From the documentation:

The following methods only apply when an instance of the class containing the method (a so-called descriptor class) appears in an owner class (the descriptor must be in either the owner’s class dictionary or in the class dictionary for one of its parents).

So descriptors (ie. objects that implement __get__, __set__ or __delete__) must be members of a class, NOT an instance.

With the followinging changes:

Temperature.celsius2 = Celsius()

T = Temperature()
print('T.celsius:', T.celsius)
print('T.celsius1:', T.celsius1)
print('T.celsius2:', T.celsius2)

The output is:

T.celsius: 0.0
T.celsius1: <__main__.Celsius object at 0x7fab8c8d0fd0>
T.celsius2:, 0.0

More links:

Community
  • 1
  • 1
codeape
  • 97,830
  • 24
  • 159
  • 188
2

T.celcius as an attribute of the class Temperature so it returns the result of the __get__ method of T.celcius as expected.

T.celsius1 is an attribute of the instance T so it returns the variable itself since descriptors are only invoked for new style objects or classes.

The __call__ method would be used if you were to do T.celsius().

jamylak
  • 128,818
  • 30
  • 231
  • 230
0

If you try that, you will see same result.

print('T.celsius:', T.celsius)
print('T.celsius1:', T.celsius1.value)

What is the purpose of self?

Community
  • 1
  • 1
Selin
  • 616
  • 2
  • 9
  • 23
0

As others said, descriptor instances are meant to be used as class attributes.

class Temperature(object):
    celsius = Celsius()
    def __init__(self):
       self.celsius1 = Celsius()

If you want self.celsius1 to have custom behavior, override __getattr__ method:

class Temperature(object):
    celsius = Celsius()
    def __getattr__(self, name):
       if name == 'celsius1':
           return ...
warvariuc
  • 57,116
  • 41
  • 173
  • 227