3.15 __getattr__#

This lesson describes the usage of __getattr__

If a class (more correctly ab object) does not have an attribute and we try to access this attribute we will get AttributeError.

class Human:
    pass


h = Human()

# uncomment following 1 line
# h.horns

The Human class does not have an attribute horns and therefore when we run h.horns, we will get AttributeError

However, if we want to avoid such an error, we can overwrite __getattr__ method of the class. This method must take one input argument.

class Human:
    def __getattr__(self, item):
        print(f"attribute {item} has not been set to Human")
        return


h = Human()
print(h.horns)
attribute horns has not been set to Human
None

When python tries to search attributes of a class, then __getattr__ method is called at the end of its search. If this method is not overwritten by the user, python will raise AttributeError, as it was done earlier.

One advantage/usage of this method is what we can call dynamic attribute creation.

TempUnitConverter = {
    "FAHRENHEIT": {
        "Fahrenheit": lambda fahrenheit: fahrenheit,  # fahrenheit to Centigrade
        "Kelvin": lambda fahrenheit: [(x + 459.67) * 5/9 for x in fahrenheit],  # fahrenheit to kelvin
        "Centigrade": lambda fahrenheit: [(x - 32.0) / 1.8 for x in fahrenheit]  # fahrenheit to Centigrade
    },
    "KELVIN": {
        "Fahrenheit": lambda kelvin: [x * 9/5 - 459.67 for x in kelvin],  # kelvin to fahrenheit
        "Kelvin": lambda k: k,     # Kelvin to Kelvin
        "Centigrade": lambda kelvin: [x - 273.15 for x in kelvin]  # kelvin to Centigrade}
    },
    "CENTIGRADE": {
        "Fahrenheit": lambda centigrade: [x * 1.8 + 32.0 for x in centigrade],  # Centigrade to fahrenheit
        "Kelvin": lambda centigrade: [x + 273.15 for x in centigrade],  # Centigrade to kelvin
        "Centigrade": lambda centigrade: centigrade
    }
}


class Temperature(object):
    """
    The idea is to write the conversion functions in a dictionary and
    then dynamically create attribute if the attribute
    is present in converter as key otherwise raise WongUnitError.
    converts temperature among units [kelvin, centigrade, fahrenheit]

    """

    def __init__(self, val, input_unit):
        self.val = val

        self.input_unit = input_unit

    def __getattr__(self, out_unit):
        # pycharm calls this method for its own working, executing default behaviour at such calls
        if out_unit.startswith('_'):
            return self.__getattribute__(out_unit)
        else:
            if out_unit not in TempUnitConverter[self.input_unit]:
                raise ValueError(f"output in {out_unit} is not allowed. Allowed units are: ", self.allowed)

            val = TempUnitConverter[self.input_unit][str(out_unit)](self.val)
            return val

    @property
    def allowed(self):
        return list(list(TempUnitConverter.values())[0].keys())

    @property
    def input_unit(self):
        return self._input_unit

    @input_unit.setter
    def input_unit(self, in_unit):
        if in_unit.upper() == 'CELSIUS':
            in_unit = 'CENTIGRADE'
        if in_unit.upper() not in TempUnitConverter:
            raise ValueError(f"Input in {in_unit} is not allowed", self.allowed)
        self._input_unit = in_unit.upper()


temp = [i for i in range(10)]
T = Temperature(temp, 'Centigrade')

print(T.Kelvin)
[273.15, 274.15, 275.15, 276.15, 277.15, 278.15, 279.15, 280.15, 281.15, 282.15]
print(T.Fahrenheit)
[32.0, 33.8, 35.6, 37.4, 39.2, 41.0, 42.8, 44.6, 46.4, 48.2]
print(T.Centigrade)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Above we did not explicitly defined Kelvin, Fahrenheit or Centigrade attributes of the Temperature class. but these attributes are created after __getattr__ method is called.

T = Temperature(temp, 'Fahrenheit')
print(T.Centigrade)
print(T.Kelvin)
[-17.77777777777778, -17.22222222222222, -16.666666666666668, -16.11111111111111, -15.555555555555555, -15.0, -14.444444444444445, -13.88888888888889, -13.333333333333332, -12.777777777777777]
[255.3722222222222, 255.92777777777778, 256.48333333333335, 257.0388888888889, 257.59444444444443, 258.15, 258.7055555555555, 259.2611111111111, 259.81666666666666, 260.3722222222222]
T = Temperature(temp, 'Kelvin')
print(T.Centigrade)
print(T.Fahrenheit)
[-273.15, -272.15, -271.15, -270.15, -269.15, -268.15, -267.15, -266.15, -265.15, -264.15]
[-459.67, -457.87, -456.07, -454.27000000000004, -452.47, -450.67, -448.87, -447.07, -445.27000000000004, -443.47]
Questions:
  • Why Temperature(temp, ‘Celsius’).Kelvin works but not Temperature(temp, ‘Celsius’).Celsius?

  • Change the Temperature class so that T.centigrade gives same answer as that of T.Centigrade.

  • Change the Temperature class so that T.Celsius also works.

Total running time of the script: ( 0 minutes 0.004 seconds)

Gallery generated by Sphinx-Gallery