Is it true that self.temperature (an instance variable) is actually referencing the class variable temperature?
You mean attribute, not variable. But yes.
If so, then why?
Because instances of the class do not have a temperature
attribute (they only have a _temperature
attribute, and so after Python fails to find the attribute in the instance, it checks the class as well.
Perhaps you are being thrown off because the __init__
method assigns to self.temperature
. However, this does not create a temperature
attribute on the instance, because the property is found first.
There are complicated rules for how the lookup of attributes works; they're designed to make it possible to do what you want - for example, to make it possible for property
to work.
I just don't understand how an instance variable with an assigned value (current_temp) could be actually referencing a class variable.
I don't see current_temp
in your code, so I can't help with that.
Part of why the code is confusing is because it does things in a very non-standard way. Normally, property
is used in its decorator form, which looks like this:
class Celsius:
def __init__(self, current_temp = 25):
# set the underlying value directly; don't use the property
# although you *can*, but it's arguably clearer this way
self._temperature = current_temp
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
# the getter is given the name that the property should have.
@property
def temperature(self):
print("Getting value")
return self._temperature
# The setter is "attached" to the getter with this syntax
@temperature.setter
# It doesn't matter that this has the same name, because of how the
# internal attachment logic works.
def temperature(self, value):
if value < -273:
raise ValueError("Temperature below -273 is not possible")
print("Setting value")
self._temperature = value # good idea to actually use the value!
Bonus: arguably, a better design is to have separate celcius
and fahrenheit
properties, and the ability to create an instance from either a celcius or fahrenheit value (use staticmethod
to create factory methods). That looks like:
class Temperature:
# Secret implementation detail: we store the value in celcius.
# We could have chosen fahrenheit instead, and do the conversions
# the other way around. Users should not call this directly.
def __init__(self, c):
# Actually, I lied. One good reason to use the property in
# the init method is to enforce bounds checking.
self.celcius = c
# We offer factory methods that let the user be explicit about
# how the initial value is provided.
@staticmethod
def in_celcius(c):
return Temperature(c)
@staticmethod
def in_fahrenheit(f):
return Temperature((f - 32) * 5/9)
# Now we define properties that let us get and set the value
# as either a celcius value or a fahrenheit one.
@property
def celcius(self):
return self._celcius
@celcius.setter
def celcius(self, c):
if c < -273:
raise ValueError("below absolute zero")
self._celcius = c
@property
def fahrenheit(self):
return (self._celcius * 9/5) + 32
@fahrenheit.setter
def fahrenheit(self, f):
# use the other property to reuse the bounds-checking logic.
self.celcius = (f - 32) * 5/9