2

when learning python property decorator in this link, I stumbled upon following lines of code:

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature       

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

    def get_temperature(self):
        print("Getting value")
        return self._temperature

    def set_temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        print("Setting value")
        self._temperature = value

    temperature = property(get_temperature,set_temperature)

this code basically let you modify/add restraint to a Celsius object, without having anybody who inherited Celsius class refactor their code.

why does it define a class variable temperature first, rather than just let self.temperature = property(get_temperature,set_temperature) and done?

EDIT: Due to conflict opinions in comments, I will now restore the code to original state, regardless if there is a typo, to make it easy for people read this afterwards.

Sajuuk
  • 2,667
  • 3
  • 22
  • 34
  • should be 'self._tem...' in `__init__`? `_temperature` does not initialized at first – Jacky1205 Jun 13 '17 at 10:31
  • And I remember that property only works with new style class and object. – Jacky1205 Jun 13 '17 at 10:34
  • there is typo in in the above link you provided. you need to update your code according to the last piece of code . it should be `self._temperature...` in `__init__` – Jacky1205 Jun 13 '17 at 10:40
  • @Jacky That is not a typo. Note that with the above code you could do this without an error, which is bad: `t=Celsius(-500)`. Typically the initializer should use the property to get or set the private variable just like any other method would. – Rick Jun 14 '17 at 01:52
  • @RickTeachey so are you saying the above code was not using the property approach correctly? – Sajuuk Jun 14 '17 at 01:56
  • @Hiagara it is not initializing the property correctly, yes, but to be more specific. – Rick Jun 14 '17 at 02:11
  • The original code (displayed now) resolved that particular problem. – Rick Jun 14 '17 at 02:15

3 Answers3

1

In the snippet above there are two things addressed:

1. Setting temperature during object creation using parameterized constructor.

def __init__(self, temperature = 0):

In this case Celsius with temperature 8 will be created as:

t = Celsius(8)

2. Setting and retrieving temperature using object property using

temperature = property(get_temperature,set_temperature)

In this case Celsius with temperature 8 will be created, set/retrieved as:

t = Celsius()
t.set_temperature(8)
print t.get_temperature()
Community
  • 1
  • 1
1

The answer lies in the descriptor protocol and attribute lookup order. If you did this:

class Celsius:
    def __init__(self, temperature): 
        self.temperature = property(get_temperature,set_temperature)
        self.temperature = temperature
# etc etc

It will not behave the way you expect. At all.

t = Celsius(100)
t.temperature = -500 # NO ERROR! BROKEN!

WHY? Because you overwrote the property object with the number that was provided to the initializer. Observe:

get = lambda *args: None # dummy getter
set = lambda *args: None # dummy setter
p = property(get, set) # dummy property

A property should be an instance of Property:

print(type(p).__name__)
# Property

But your temperature isn't a property anymore:

print(type(t.temperature).__name__)
# int

You overwrote it with this line:

self.temperature = temperature 
# note that temperature is an int or float

Sprinkle some print statements to see what is going on:

class Celsius:
    def __init__(self, temperature): 
        self.temperature = property(get_temperature,set_temperature)
        print(type(self.temperature).__name__) # Property
        self.temperature = temperature
        print(type(self.temperature).__name__) # no longer a Property!
# etc etc

Therefore, the property object needs to be stored at the class level so it doesn't get overwritten at the instance level. When a class level property object is accessed at the instance level, the descriptor protocol is automatically invoked (a Property is a type of descriptor; descriptors have unusual behavior so go study them carefully).

Learn more about class level objects vs. instance level objects at this much more detailed answer.

Also note that the temperature needs to be set in the initializer using the property. Do not do this:

class Celsius: 
    def __init__(self, temperature):
        self._temperature = temperature

Using this code you could do this without an error, which is bad: t=Celsius(-500). Typically the initializer should use the property to get or set the private variable just like any other method would:

        self.temperature = temperature

Now an invalid initial temperature will cause an error, as expected.

Rick
  • 43,029
  • 15
  • 76
  • 119
  • to me, looks like there is indeed a typo, because after instantiate the class as c, doing c.temperature would only output "Setting Value" if it is self._temperature in __init__ method. – Sajuuk Jun 14 '17 at 02:56
  • You are mistaken. – Rick Jun 14 '17 at 03:01
  • in what sense, if I may ask? in fact at this moment I am highly doubting this original code is idiomatic at all. – Sajuuk Jun 14 '17 at 03:23
  • It's obviously old. But only because it is not using the `@property` decorator. You are mistaken because if you do not use the setter inside of the initializer, you will be able to initialize with invalid values, ie, `Celsius(-500)`. – Rick Jun 14 '17 at 03:37
  • is the correct way to use setter in initializer adding `self.set_temperature(temperature)` in `__init__` method? – Sajuuk Jun 14 '17 at 06:22
  • No, just use it the original way. – Rick Jun 14 '17 at 11:41
0

Because methods are class properties. And self does not exist in that scope.

Note, that code is very out of date; you should use property as a decorator:

@property
def temperature(self):
    print("Getting value")
    return self._temperature

@temperature.setter
def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    print("Setting value")
    self._temperature = value
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895