A pythonic way of writing a library function that takes several types?

If, as a simplified example, I am writing a library to help people model populations, I might have a class, for example:

class Population: def __init__(self, t0, initial, growth): self.t0 = t0, self.initial = initial self.growth = growth 

where t0 is of type datetime. Now I want to provide a method for determining the population at a given point in time, be it a date-time or a float containing the number of seconds since t0. It would also be reasonable for the caller to provide an array of such times (if so, I think it is reasonable to assume that they will all be of the same type). There are at least two ways I can do this:

  • Method for each type

     def at_raw(self, t): if not isinstance(t, collections.Iterable): t = numpy.array([t]) return self.initial*numpy.exp(self.growth*t) def at_datetime(self, t): if not isinstance(t, collections.Iterable): t = [t] dt = numpy.array([(t1-self.t0).total_seconds() for t1 in t]) return self.at_raw(dt) 
  • Universal method

     def at(self, t): if isinstance(t, datetime): t = (t-self.t0).total_seconds() if isinstance(t, collections.Iterable): if isinstance(t[0], datetime): t = [(t1-self.t0).total_seconds() for t1 in t] else: t = np.array([t]) return self.initial*numpy.exp(self.growth*t) 

Either this will work, but I'm not sure if there are more pythons. I saw some suggestions that type checking indicates a bad design that method 1 would suggest, but since this is a library intended for use by others, method 2 is likely to be more useful.

Please note that it is necessary to maintain the time specified as a float, even if the library itself uses this function, for example, I could implement a method that the root finds for fixed points in a more complex model, where a float representation is preferred.Thank you in advance for any suggestions or tips.

+6
source share
1 answer

I believe you can just stick with the Python Duck Typing philosophy here.

 def at(self, t): def get_arr(t): try: # Iterate over me return [get_arr(t1)[0] for t1 in t] except TypeError: #Opps am not Iterable pass try: # you can subtract datetime object return [(t-self.t0).total_seconds()] except TypeError: #Opps am not a datetime object pass # I am just a float return [t] self.initial*numpy.exp(self.growth*np.array(get_arr(t))) 

It is important how you order cases

  • Specific cases must precede general cases.

     def foo(num): """Convert a string implementation to Python Object""" try: #First check if its an Integer return int(num) except ValueError: #Well not an Integer pass try: #Check if its a float return float(num) except ValueError: pass #Invalid Number raise TypeError("Invalid Number Specified") 
  • The default value should be the final case.

  • If consecutive cases are mutually exclusive, order them by chance.
  • Prepare for the unexpected by raising an exception. In the end, Errors should never pass silently.
+5
source

Source: https://habr.com/ru/post/955924/


All Articles