0

I was just wondering what would be the preferred way in Python to make a group of arguments of a function optional, but only as the whole group. Meaning: they have to either all be given, or none.

For an example, let's say I want to make a print function, that takes a message string as first positional argument and optionally a file-like object and an encoding as second and third arguments.

Now I want this function to print to stdout if no file is given, and to the file otherwise. The tricky bit is this: I want this function to always require an encoding to be specified whenever a file is used. And calling this function with an encoding, but no file should also be forbidden.

In Java, I could overload the function and give implementations for both valid variants:

public void print(string message);
public void print(string message, File f, string encoding);

This allows me to call this function in exactly the two ways I want to be possible, with either one or all three arguments.

In Python, I can make single arguments optional by supplying a default value, but I cannot group them together.

def print(msg, file=None, encoding=None)

allows me to call the function by providing a message and none, both or just any one of the other parameters:

print("test")
print("test", file=someFile)
print("test", encoding="utf-8")
print("test", file=someFile, encoding="utf-8")

These are all valid calls to the Python declaration above, even though with my implementation, setting an encoding or file without the other one might make no sense.

I am aware that I could simply check both optionals for an invalid default value and raise an Exception at runtime whenever I find only one is set, but I think that is bad for a couple of reasons:

  1. The Exception is raised only if the invalid call is executed, so it might not occur during testing.
  2. I have no way of telling that both parameters are required as a pair by just looking at the declaration or an auto-generated quick reference without diving into the implementation.
  3. No code analysis tool would be able to warn me about an invalid call.

So is there any better way to syntactically specify that a number of optional arguments are grouped together?

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Sorontik
  • 69
  • 1
  • 6
  • Maybe you can use a tuple as one parameter and then call the indices separately inside the function? – Ayush Jan 19 '22 at 15:30
  • 1
    `if file is not None and encoding is None: raise TypeError("print() missing 1 argument: 'encoding'`? – Tomerikoo Jan 19 '22 at 15:34
  • There's nothing in Python that prevents you from manually checking whether a combination of arguments makes sense. Even in large libraries you often read docs that say something like "arg X ist only valid together with arg Y" etc – timgeb Jan 19 '22 at 15:37
  • Not sure if a duplicate target but related: [Multiple optional arguments python](https://stackoverflow.com/q/43279256/6045800) – Tomerikoo Jan 19 '22 at 15:42
  • @Tomerikoo as i wrote in my question, i am aware of that possibility, but i also listed a couple of reasons, why i don't think this is a **good** solution and i would not consider the other question a duplicate, because the other question focuses on how to determine the "variant" of the function that should be executed at runtime, whereas i am looking for a possibility to write down such requirements in a concise syntactic way, that is readable/obvious for humans as well as automated code checkers before runtime (at what would be "compile time" in languages like C/C++ or java) – Sorontik Jan 19 '22 at 17:09
  • Also thank you @Tomerikoo for your edits to my question, but can you explain why you remove the overloading tag since that is, in general, one solution to what i'm trying to achieve? – Sorontik Jan 19 '22 at 17:10
  • Most simply - there is no overloading in Python... You gave an example of overloading in Java... That's not what the question is *about* which is what tags are for. Regarding your first comment, this is why I didn't post an answer and didn't flag your question as duplicate. Simply putting the link here for reference as they are somewhat related (although to be honest I find that question and its answers quite confusing and unclear) – Tomerikoo Jan 19 '22 at 17:14
  • You can also see [range non-default parameter follows default one](https://stackoverflow.com/q/43999181/6045800) as [`range`](https://docs.python.org/3/library/functions.html#func-range) is a good example for something that *looks like* overloading in Python. Not sure though if it aligns with your requirements... – Tomerikoo Jan 19 '22 at 17:21
  • Finally, did you see the elaborate [Python function overloading](https://stackoverflow.com/q/6434482/6045800)? – Tomerikoo Jan 19 '22 at 17:23
  • i did not see it until now, but after reading it I realise that overloading is in fact, not what i actually want. I don't want to provide different, independent implementations based on the arguments, i want to require that a group of arguments are either all specified explicitly or are all omitted and thus fall back to their default values. Doing this by overloading would mean both implementations would either be basically copy-paste or one would essentially be a wrapper for the other one, which is similar to the answer I propose below – Sorontik Jan 19 '22 at 18:27

3 Answers3

1

Python is not supporting overloading methods. And there is not a really good way to simulate an overloading design. So best you can do is using if statements with different arguments. Like you do in your method.

Or you can use **kwargs as argument and use if only the desired argument is defined.

def a_very_important_method(**kwargs)
    if kwargs["arg1"] is not None:
        # logic
    if kwargs["arg2"] is not None:
        # another logic

a_very_important_method(arg1="value1", arg2="value2")
  • Well, scattering a cascade of conditionals throughout a function to enforce implicit rules about arguments does not seem very readable, obvious or "beatiful" and thus, does not sound very pythonic to me – Sorontik Jan 19 '22 at 17:18
  • Sometimes there is nothing to do :( – Önder ALKAN Jan 20 '22 at 06:39
0

I mean you could make one parameter expect a tuple as input. Like idk an 2D-array might have a size attribute which requires an input in the shape (x, y). Though that won't save you from checking at runtime whether the supplied values make any sense, does it?

haxor789
  • 604
  • 6
  • 18
  • i considered this, but it would also be very difficult to document, which values are excpected inside the tuple/list, how many and in which order, so this would not be very readable – Sorontik Jan 19 '22 at 16:51
0

After reading the other answers, it seems to me like the most simple and readable solution would be to write the function with all parameters mandatory and then add a second, "wrapper"- function which has a reduced set of parameters, passes these arguments to the original function on and also gives default values for the other parameters:

def print(msg, file, encoding):
    # no default values here, so no parameter is optional
    pass

def printout(msg):
    # forward the argument and provide default values for the others
    print(msg, sys.stdout, "")
Sorontik
  • 69
  • 1
  • 6