# Build object with different input, using super-class and sub-class style, Python 3

I want to model a object of air, the temperature of which can be given in 3 different ways:

1. if the value is known (assuming the temperature is a constant)
2. if a curve of the values with different time is known (using interpolation)
3. if the formula of the temperature vs time is known.

My code is below:

from script.interpolate import interp1d
from importlib import import_module

class Air1:
# case 1: temperature, which is a constant value, is given in the input.
def __init__(self, meta: dict):
self.city = meta['city']
self.temperature = meta['temperature']

class Air2(Air1):
# case 2: a curve of temperature vs time is given in meta.
# times is a list, temperatures is a list.
# so self.get_temperature is an interpolated function.
# To get a new temperature, I can use new_temp = self.get_temperature(new_time)
def __init__(self, meta: dict):
super().__init__(meta=meta)
times = meta['times']
temperatures = meta['temperatures']
self.get_temperature = interp1d(times, temperatures)

class Air3(Air1):
# case 3: the formula to calculate temperature from time is known.
# this formula is implemented in a python file called file_of_fomurla.
# so self.get_temperature is an imported function.
# To get a new temperature, I can use new_temp = self.get_temperature(new_time)
def __init__(self, meta: dict):
super().__init__(meta=meta)
file_of_formula = meta['file_of_formula']
self.get_temperature = import_module(file_of_formula + '.my_function')


One thing I have noticed in my code is that .temperature should only be in Air1, since Air2 and Air3 both just have a function to calculate temperature value based on time. However, like in my code, Air2 and Air3 are sub classes of Air1, but their input meta will not have the key 'temperature'. So this is an error.

Do you have any better way to implement the physics behind into the model. Maybe using some abstract class?

As you yourself have noted, the inheritance isn't benefiting you at all. Just write one class.

Passing meta as a dict is unidiomatic. Use **kwargs or parameters with defaults instead.

Consider dropping the get_… prefix.

Also consider passing the formula as code (e.g. a lambda) rather than referencing a module by name.

from script.interpolate import interp1d
from importlib import import_module

class Air:
def __init__(self, city, **kwargs):
self.city = city
self.temperature = (
lambda time: kwargs['temperature'] if 'temperature' in kwargs else
interp1d(kwargs['times'], kwargs['temperatures']) if 'times' in kwargs else
import_module(kwargs['file_of_formula'] + '.my_function') if 'file_of_formula' in kwargs else
kwargs['formula']
)

• @MaartenFabré They are all meant to be callable, taking a time parameter. – 200_success Sep 28 '18 at 14:46
• thanks for your tips. There is a small issue, in Air1, temperature is a value, so it should not be a function with a time parameter. And by the way, could you give me an example when kwarts['formula'] is used, assuming the formula is temperature = 2 * time + 1. Thanks again. – aura Sep 28 '18 at 15:33
• The object must behave consistently, regardless of whether the temperature is constant or varying. A constant temperature is just a special case where it returns the same value no matter what the time is. So, it's a method that ignores its time parameter. – 200_success Sep 28 '18 at 17:31
• To instantiate an Air object with an ad hoc formula: gotham = Air('Gotham', formula=lambda t: 2 * t + 1). – 200_success Sep 28 '18 at 17:32

you can use define a private attribute _temperature to hold the temperature from meta, and set it to None if it is not defined, and define a temperature property:

class Air1:
# case 1: temperature, which is a constant value, is given in the input.
def __init__(self, meta: dict):
self.city = meta['city']
self._temperature = meta.get('temperature', None)

@property
def temperature(self):
if self._temperature is None:
raise NotImplementedError
return self._temperature