Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python property and variable assignment

Below is a class from this tutorial designed to demonstrate the functionality of the property in Python.

The class sets and gets temperature in Celsius and also converts to Fahrenheit:

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

    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 = 50

    temperature = property(get_temperature,set_temperature)

Question: Is it true that self.temperature (an instance variable) is actually referencing the class variable temperature? If so, then why?

Example:

obj = Celsius()
print(obj._temperature, obj.temperature)

Returns:

Setting value
Getting value
50 50

I just don't understand how an instance variable with an assigned value (current_temp) could be actually referencing a class variable. Sorry if I have something mistaken.

like image 557
DSH Avatar asked May 16 '26 15:05

DSH


1 Answers

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
like image 131
Karl Knechtel Avatar answered May 19 '26 04:05

Karl Knechtel



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!