Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python function that takes one compulsory argument from two choices

I have a function:

def delete(title=None, pageid=None):
    # Deletes the page given, either by its title or ID
    ...
    pass

However, my intention is for the function to take only one argument (either title OR pageid). For example, delete(title="MyPage") or delete(pageid=53342) are valid, while delete(title="MyPage", pageid=53342), is not. Zero arguments can not be passed. How would I go about doing this?

like image 600
TerryA Avatar asked Aug 28 '16 08:08

TerryA


People also ask

Can you write functions that accept multiple arguments in Python?

Luckily, you can write functions that take in more than one parameter by defining as many parameters as needed, for example: def function_name(data_1, data_2):

What are the 4 types of arguments in Python?

5 Types of Arguments in Python Function Definition:positional arguments. arbitrary positional arguments. arbitrary keyword arguments.

Can a function take multiple arguments?

Functions can accept more than one argument. When calling a function, you're able to pass multiple arguments to the function; each argument gets stored in a separate parameter and used as a discrete variable within the function.


2 Answers

There is no Python syntax that'll let you define exclusive-or arguments, no. You'd have to explicitly raise an exception is both or none are specified:

if (title and pageid) or not (title or pageid):
    raise ValueError('Can only delete by title OR pageid')

The full expression can become a little tedious, especially if title or pageid could be set to 0 or another falsey value that is valid, so you could capture the above in a utility function:

from typing import Any

def exclusive_or(left: Any, right: Any) -> bool:
    """Test if either left or right is true, but not both"""
    return (left or right) and not (left and right)

Note that this is the inverse test of what you needed for the if test, so don't forget to add not:

if not exclusive_or(title, pageid):
    # ...

which is a lot easier to use when having to test for not None:

if not exclusive_or(title is not None, pageid is not None):
    # ...

Note that I used type hints to add information to the function for type checkers and IDEs to see what kind of arguments the function takes or what the return value type is.

With type hints, you can also mark up your original function and tell those tools that your function only takes one or the other argument, by using @overload:

@overload
def delete(title: str) -> None: ...

@overload
def delete(pageid: int) -> None: ...

def delete(title: Optional[str] = None, pageid: Optional[int] = None) -> None:
    """Deletes the page given, either by its title or ID"""
    ...
    pass

In an IDE like PyCharm or Visual Studio Code (with the Pylance extension installed) would show you real-time information about the function as you type:

Screenshot of a snippet of code in Visual Studio Code reading "delete()', with a tooltip showing "(title: str) -> None" and the documentation string for the delete function, together with a counter indicating this is one of two possible completions for this function

Another screenshot of the same snippet of code, this time with the tooltip changed to show "(pageid: int) -> None", with the counter indicating that this is the second of two possible completions

This kind of tool integration would make it easier to use your function without triggering an exception.

like image 158
Martijn Pieters Avatar answered Nov 15 '22 00:11

Martijn Pieters


I don't think there's any way to do this in the function signature itself. However, you could always have a check inside your function to see if both arguments are set

def delete(title=None, pageid=None):
# Deletes the page given, either by its title or ID
    if title and pageid:
        # throw error or return an error value
pass

Arguably a better way of doing it would be to define 2 methods that call the delete method

def delete_by_title(title):
    delete(title=title)

def delete_by_id(id):
    delete(pageid=id)

The second method won't stop people calling the delete function directly, so if that's really important to you I'd advise having it throw an exception as per my first example, or else a combination of the 2.

like image 21
Luke K Avatar answered Nov 15 '22 00:11

Luke K