0

If I design a function like

def f(a, b):
  ...

a must be in [1,2,3]. If I pass 4 to parameter a, it should raise an exception. Is there a pythonic way to limit the value range of a function argument?


Added Please note, is there a more pythonic way to implement it? Not just a simple assert or if in. For instance, decorator or maybe some syntactic sugar.

f1msch
  • 509
  • 2
  • 12
  • `if a not in {1,2,3}: raise ValueError("...")` – matszwecja Aug 24 '23 at 07:05
  • You check the value and raise an error. If the range is contiguous, then `range` is good. Sets are good for more random things. Something like `if not isinstance(a, int) or a not in [1,2,3]: raise ValueError("no good")`. – tdelaney Aug 24 '23 at 07:06
  • @tdelaney `not isinstance(a, int)` is redundant because `a not in [1,2,3]` covers the check already. – blhsing Aug 24 '23 at 07:08
  • For the Added note: what is a more pythonic way to solve a problem than the simplest solution you can think of? Why would you want to use a decorator when the language itself has plenty of simpler tools to achieve what you are asking for. – jlgarcia Aug 24 '23 at 07:31
  • I just saw that you updated your question, so I updated my answer for you too. Please check it out. Thank you! – Freeman Aug 24 '23 at 07:39
  • @blhsing - it would be needed for a set which I didn't include in the example because of space. To be efficient, the set would be created once outside of the compare so that its not rebuilt on each call. The list has the same problem, but its a bit faster than set. – tdelaney Aug 24 '23 at 15:13

2 Answers2

2

Yes, and also you can raise an exception if the input value is outside the desired range. For example, let's say you have a function called f that takes two integer parameters, a and b. To limit the range of a, you can use an annotation like a: int to specify that a should be an integer. Then, you can add an if statement to check if a is within the desired range. If it's not, a ValueError will be raised with a specific error message. So, if you call the function with an invalid value for a, like 4, it will raise an exception.

def f(a: int, b: int) -> int:
    if a not in [1, 2, 3]:
        raise ValueError("Invalid value for 'a'. Expected 1, 2, or 3.")
    
    #rest of your functions
    
    return result

update

In this new example, I created a decorator called value_range that you can use to limit the range of a function argument, and it's super easy to use, just apply the decorator to your function f and specify the range values you want, as you see if you pass a value outside that range, it'll raise a ValueError. It's a really neat and pythonic way to keep things in check without messy assert or if statements!

from typing import Union

def value_range(min_value: int, max_value: int):
    def decorator(func):
        def wrapper(a: Union[int, float], b):
            if not min_value <= a <= max_value:
                raise ValueError(f"Invalid value for 'a'. It should be between {min_value} and {max_value}.")
            return func(a, b)
        return wrapper
    return decorator

@value_range(1, 3)
def f(a: int, b):
    #rest of your functions
    pass
Freeman
  • 9,464
  • 7
  • 35
  • 58
  • I could suggest making list a set (because why not) instead and adding inputted value of a in the error message. – matszwecja Aug 24 '23 at 07:12
  • 1
    @matszwecja yes exactly, and then we will use it like this : `if a not in valid_values: raise ValueError(f"Invalid value '{a}' for 'a'. Expected one of {valid_values}.")` – Freeman Aug 24 '23 at 07:14
0

Numerous ways to do this, but I really like the assert clause:

def f(a, b):
    assert a in range(3), "Exception message raised because parameter 'a' is not in [0, 1, 2]"
    # Your code

When a is not in your defined valid range, the function will raise an AssertionError, displaying your message.

jlgarcia
  • 333
  • 6
  • Commonly accepted practice in Python is to use `TypeError`s and `ValueError`s – matszwecja Aug 24 '23 at 07:10
  • 2
    Be careful, assertions [can be disabled](https://stackoverflow.com/questions/1273211/disable-assertions-in-python) – sudden_appearance Aug 24 '23 at 07:16
  • When code is used in production the Python interpreter is typically started with the -O (or -OO) option which will have the effect of disabling *assert* - i.e., it becomes a noop. *assert* is very useful during development but not in production – DarkKnight Aug 24 '23 at 07:50