0

(EDITED FOR CLARITY)

I am trying to solve this issue with passing default parameters.

I have functions creating plots with matplotlib. These functions accept parameters and some of those have default values:

def radar_with_CI(values, categories, group=0):
    ...


def multi_radar_with_CI(values,
                        categories,
                        fname: str,
                        series_names="Series",
                        pth="t:/Projects/dev/Plotter/"):
    ...


def overlay_radar_with_CI(values,
                          categories,
                          fname: str,
                          series_names="Series",
                          pth="t:/Projects/dev/Plotter/"):
    ...

Then there is a master function, that aggregates parameters and runs different functions based on 'mode'.

def radar(values,
          categories,
          fname,
          series_names="Series",
          pth="",
          mode="all",
          group=None):

    if mode == "single":
        radar_with_CI(values, categories, group=group)

    if mode == "one-by-one" or mode == "all":
        multi_radar_with_CI(values,
                            categories,
                            series_names=series_names,
                            fname=fname,
                            pth=pth)

    if mode == "overlay" or mode == "all":
        overlay_radar_with_CI(values,
                              categories,
                              series_names=series_names,
                              fname=fname,
                              pth=pth)

Thing is, I need a default parameter for e.g. series_names, but I need this default parameter both in the master function and in the plotting functions themselves to be able to call them both through master function and separately.

In the code above my current solution is implemented: I made the parameter default in both the master and plotting functions.

THE QUESTION:

Is this a correct solution? Or is it bad practice to stack default parameters on themselves (in other words to send a parameter with the same value as is the default one)? Is this a place for using args or kwargs?

Thanks for any advice. If there is something unclear, let me know in the comments and I will edit this post.

  • 1
    Sorry, I don't understand the problem. Could you add a short sample code and explain what it should do but does not? – Michael Butscher Apr 07 '22 at 12:33
  • 1
    Just a side note - your last two `if`s don't really make sense... the `or mode == "all"` is always going to cover both... did you mean them to be `and` ? – Jon Clements Apr 07 '22 at 12:38
  • What is the default parameter you want to pass from master to make_plot_c function? Name that parameter. – Mithilesh_Kunal Apr 07 '22 at 13:18
  • @MichaelButscher edits were made, please check the updated question. :-) – Ondřej Janča Apr 07 '22 at 13:48
  • @JonClements it is an intended design. 'all' mode calls all functions except for the first one. So the last two ifs are true when either the function in question is called specifically or 'all' mode is active. – Ondřej Janča Apr 07 '22 at 13:50
  • 1
    It's not clear there *should* be a master function. What's the benefit of calling `radar(..., mode="single")` over calling `radar_with_CI(...)` directly? – chepner Apr 07 '22 at 14:09
  • @chepner you are right that at this point it is not necessary. Since there will be more plotting functions, I just wanted to have one place to operate that all. At this point it is redundant. – Ondřej Janča Apr 07 '22 at 14:38
  • It doesn't really matter how many functions you have. The only useful thing about `radar` is the fact that it might call more than one specialized function depending on the value of `mode`, and even that can be replaced by an appropriate specialized function. `def radar_all(...): radar_one(...); radar_two(...)`. – chepner Apr 07 '22 at 14:40
  • With one master function, the *caller* still has to decide what `mode` argument to pass, which is no simpler than deciding which appropriate function to call instead. – chepner Apr 07 '22 at 14:41
  • @chepner to me this seems simpler since I don't have to comment/uncomment functions in main, change the parameters, copy the same parameters into multiple functions, and have to check the whole main for missed parts of code each time I want to run the script. Now I just select mode instead of activating several functions. The merit of the question was default arguments though, not the function itself. – Ondřej Janča Apr 07 '22 at 16:06

2 Answers2

1

Hi I would make a Master Class like so:

class Master:
    def __init__(self,mode,firstName='Tom',lastName='Smith'):
        self.firstName = firstName
        self.lastName = lastName
        self.mode = mode

        if self.mode == 'ModeA':
            self.functionOne()

        if self.mode == 'ModeB':
            self.functionTwo()

    def functionOne(self):
        print(self.firstName + ' ' + self.lastName)

    def functionTwo(self):
        print(self.lastName + ', ' + self.firstName)

a = Master('ModeA',lastName='Porter')
a = Master('ModeB',lastName='Porter')
a = Master('ModeB',firstName='Jeff',lastName='Charles')    

Output:

Tom Porter
Porter, Tom
Charles, Jeff

@Ondřej Janča Does this not do what you want?

jemsot
  • 11
  • 2
  • I am sorry but I don't see how this relates to my question, which was whether propagating default parameters through function calls is okay, or whether it is a place for **kwargs. – Ondřej Janča Apr 07 '22 at 15:54
  • You have completely changed the question from the original, I have provided a code structure that does not require propagation of default values through two functions by defining a Class. – jemsot Apr 07 '22 at 20:53
  • By defining and using a Class it will be easier to import and reuse this python script. – jemsot Apr 07 '22 at 20:55
  • 1
    I agree with chepner that what you have done with the master function is no more convenient than simply calling the functions themselves. In your original post you mentioned that you wanted "pythonic". The pythonic thing would be to encapsulate all logic in a Class and then use it from the terminal Imho. – jemsot Apr 07 '22 at 21:04
  • I understand now how your solution applies to my problem. I am sorry, I can see how my question was not asked clearly and what caused the misunderstanding in the first place. Regarding the class, I do not understand clearly though, how is a class better than using a function here. Could you elaborate on that, please? If I used a class like in your example, an object would be created each time I wanted to run the script with different settings. What is the advantage here? – Ondřej Janča Apr 08 '22 at 10:44
  • I think that what you have done is create a master function that calls other functions using similar parameters. I think that therefore it makes more sense to turn the master function to an object and the other functions to methods and the input parameters to class attributes. The main advantage is that the encapsulation of the class means it is easier to understand than having 4 or more functions that have a dependency that is not explicit in the same way with the Class. Also as mentioned before it is easier to import and reuse the code as a class. – jemsot Apr 08 '22 at 13:24
  • 1
    I have still used default values for the inputs so you don't have to define them when creating new objects for the class. -- which I thought was your issue, that you didn't want to type out long function calls for some reason – jemsot Apr 08 '22 at 13:26
1

Repeating yourself is always a bad idea (defining the default values multiple times).

I suggest defining the additional parameters as kwargs and unpack them in the function call:

def radar(values,
      categories,
      *,
      mode="all",
      **kwargs
      ):
if mode == "single":
    radar_with_CI(values, categories, **kwargs)

if mode == "one-by-one" or mode == "all":
    multi_radar_with_CI(values,
                        categories,
                        **kwargs)

if mode == "overlay" or mode == "all":
    overlay_radar_with_CI(values,
                          categories,
                          **kwargs)

So you can call your function radar, define one parameter (fname), and use the default values from the original function for the other parameters.

radar([1,2,3], ['A','B','C'], mode='overlay', fname='test')

Note that you will receive a TypeError if you pass a keyword argument not defined by the called function.