The focus of my question is on design.

If I have an argument that can be None and it is passed between several functions until finally being used, which function should treat it as a default argument?

For example, suppose the following code block:

def foo(a):
 b = # some code block to assign b
 return bar(a, b)

def bar(a, b):
 c = # some code block to assign c
 return baz(a, b, c)

def baz(a, b, c):
 return a + b + c

The default value should be set only in foo and all the other functions must expect the argument as mandatory (e.g., foo(a=None); bar(a, b); baz(a, b, c))? Or is there a better design?

And if type verification was needed, what is the most suitable function for this?

1 Answer 1


To make a sensible decisions about default arguments, one has to know the context how these functions are going to be used!.

If foo is going to be part of the public API of a module or component, then providing a default argument a=None assumes there is a frequent usage scenario where foo() will be called in this form, without any parameters. The same measure holds for bar and baz:

  • are those functions are just used "internally"? Then you know precisely whether providing a default argument will save you some repetitional code, or if bar(a, b) is only called once or twice, maybe only within foo, in which case a default argument is not beneficial

  • or are those functions also part of the public API? Then you need to estimate how their typical usage scenario will look like (for which you need some real context). If you want to provide the ability for bar to be called like bar(b="some value"), then providing the signature bar(a=none, b) is necessary, regardless of the default parameter in foo.

So for making decisions about default arguments, don't look at the implementation side, look at the usage side!

Concerning type verification: I am not an expert for this topic in Python, but I guess if you want to add type annotations for static type analysis, IMHO it will make most sense to add them at every level of the call chain (it may be actually mandatory for making the type analysis working) . If the "innermost function" (baz) expects only arguments of a certain type, you obviously have to put the type annotations there. This, however, implies that foo and bar will only work correctly with arguments of the same type, which means for static type analysis the annotations are required there, too.

  • In reference to your last sentence, by default foo will not give warnings, but some static type analyzers have strict options to force a type annotation to be present everywhere, causing an unannotated foo to be an error. Sep 10, 2020 at 5:00
  • @MarioIshac: I suspected that, changed my answer accordingly, thanks!
    – Doc Brown
    Sep 10, 2020 at 6:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.