0

Im having trouble understanding clean way to do this. I would like a function named set_delay() that take a variety of parameters. I have 3 different "delay types" that can be set: constant, uniform, and normal. Here is what I currently have:

def set_delay_constant(delay):
    continue

def set_delay_uniform(min_delay, max_delay):
    continue

def set_delay_normal(mean, std_dev, timeout):
    continue

The problem I have with the above is that about ~80% of the code in each function is repeated. Ideas Ive seen are:

def set_delay(delay_type, delay=None, min_delay=None, max_delay=None, mean=None, std_dev=None, timeout=None):
    continue

But when I need to extend this with more delay types, I can see this getting very long and hard to read. What is the most "pythonic" way to go about this? Thank you!

Tonysres
  • 59
  • 1
  • 6
  • That code's not valid... `SyntaxError: 'continue' not properly in loop`. Did you mean `pass` instead of `continue`? – wjandrea Dec 22 '21 at 20:00
  • 1
    You may use [keyword parameters](https://stackoverflow.com/questions/42724242/how-can-i-pass-keyword-arguments-as-parameters-to-a-function). – DYZ Dec 22 '21 at 20:02
  • @wjandrea Yes just as a way to only include the function header – Tonysres Dec 22 '21 at 20:05
  • @DYZ thanks! thats exactly what I was looking for – Tonysres Dec 22 '21 at 20:07
  • 3
    I would consider three separate function the right way to do this. You can factor out the common parts to a single function that each of your three calls. – chepner Dec 22 '21 at 20:11
  • 1
    @chepner I just posted an answer to that effect. Thanks for reassuring me that I'm on the right track :) If you have anything to add from your experience, I'd appreciate it. – wjandrea Dec 22 '21 at 20:21
  • Python *doesn't have overloading* – juanpa.arrivillaga Dec 22 '21 at 20:30

2 Answers2

6

Don't use overloading. Where the problem that you're trying to solve is "about ~80% of the code in each function is repeated", the better solution is to factor out that code into its own function, call it _set_delay for example, then call it from each of the set_delay_* functions.

To me, overloading only makes sense where the parameters overlap, but none do in your case. For contrast, say I write range(10) but then I realize I want to start from 1; thanks to overloading, I can simply change it to range(1, 10). Meanwhile, if I added an argument to your proposed set_delay, I would get totally different behaviour.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
  • Well, strictly speaking, Python doesn't have overloading. But your answer hits the key issue – juanpa.arrivillaga Dec 22 '21 at 20:31
  • @juanpa I thought I might have the terminology a bit off, but what do you mean exactly? What do you call the `range` example if not overloading? I read about *multiple dispatch*, but that seems to be only about parameter type, not number of parameters. – wjandrea Dec 22 '21 at 20:35
  • 1
    @wjandrea like in Java, where you define multiple functions with the same *name* but different signatures, and the actual function that is called depends on the arguments provided to the function (usually decidd at compile time). So checkout this [C++ example on wikipedia](https://en.wikipedia.org/wiki/Function_overloading#Rules_in_function_overloading). Python doesn't support that, I'm not sure any dynamic language supports it. But usually, just providing a very flexible function signature and manually implementing the dispatch yourself is frequently what you'd do in Python. – juanpa.arrivillaga Dec 22 '21 at 20:44
  • 1
    I consider `range` something that retains its API *despite* the shortcomings, due to its long history. If it were introduced today, I suspect it would have two required positional arguments, with an optional third to specify the step size. It might also make more extensive use of keyword-only arguments. – chepner Dec 22 '21 at 20:44
  • 1
    Compare `range` to `enumerate`: `range(10)` could still be the numbers 0 to 9, with `range(10, start=5)` being the numbers 5 to 9 (though being able to have 5 and 10 "out of order" looks odd). I don't know what the best solution would be. – chepner Dec 22 '21 at 20:48
  • @juanpa Gotcha, it's not a language feature. I was thinking about adding functionality, like [this solution with `pythonlangutil.overload`](/a/29877452/4518341). – wjandrea Dec 22 '21 at 20:53
1

I would personally move to a class based structure:

from abc import ABC, abstractmethod

class Delay(ABC):
    def do_delay(self):
        self._prepare_delay()
        #put the 80% of overlapping code here

    @abstractmethod
    def _prepare_delay(self):
        pass


class ConstantDelay(Delay):
    def __init__(self, delay):
        self.delay = delay
        #custom code can go here

    def _prepare_delay(self)
        #more custom code here

class UniformDelay(Delay):
    def __init__(self, min_delay, max_delay):
        self.min_delay = min_delay
        self.max_delay = max_delay
        #custom code goes here

    def _prepare_delay(self)
        #more custom code here

#etc

Then your set_delay function would take a single argument, an instance of a Delay subclass:

def set_delay(delay):
    pass
RoadieRich
  • 6,330
  • 3
  • 35
  • 52