Sajuuk
Sajuuk

Reputation: 3039

Why is a class variable defined first before turned into an instance variable in this code?

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.

Upvotes: 2

Views: 184

Answers (3)

Rick
Rick

Reputation: 45231

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.

Upvotes: 1

Ryan D mello
Ryan D mello

Reputation: 87

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()

Upvotes: 1

Daniel Roseman
Daniel Roseman

Reputation: 599550

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

Upvotes: 0

Related Questions