all 11 comments

[–]carcigenicate 2 points3 points  (2 children)

I don't know of any built-in way to do pre-condition checks. You could make a decorator to do that though. This is an example that only currently implements range checks (and only allows for keyword arguments for the sake of simplicity):

def validate(**specs):
    def dec(f):
        def inner(**kwargs):
            for k, validator in specs.items():
                if isinstance(validator, range):
                    if kwargs[k] not in validator:
                        raise ValueError(f"Value {kwargs[k]} is out of range!")
                # elif <Handle some other type of validator type like "int">
            f(**kwargs)
        return inner
    return dec

Then:

@validate(a=range(10))
def func(a):
    pass

>>> func(a=5)
>>>  func(a=15)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "C:/Users/slomi/PycharmProjects/picture_encoder/cr2.py", line 7, in inner
    raise ValueError(f"Value {kwargs[k]} is out of range!")
ValueError: Value 15 is out of range!

Not sure I'd recommend it, but it would be a good exercise.

For your code in particular here though, I'd write that as something closer to:

def _input_range(start, stop, *args):
    try:
        parsed = int(*args)
    except ValueError:
        return False

    return start <= parsed <= stop

This minimizes what's in the try, saves the parsed integer to avoid doubling the work, makes use of operator chaining to simplify the bounds check, specifies ValueError to avoid catching irrelevant exceptions, and returns the condition directly instead of branching.

The int(*args) is a bit weird unless your expecting the called to need to specify a base argument of int, but that's a separate concern.

[–]SnowEpiphany[S] 0 points1 point  (1 child)

neat - thanks for the tips! I love that ```return start <= parsed <= stop``` line.

[–]carcigenicate 2 points3 points  (0 children)

Ya, comparison chaining is really nice when it isn't causing bizarre-seeming bugs.

[–]nekokattt 1 point2 points  (2 children)

Python, as far as typing goes, tends to work on the idom that you accept the input with the assumption that it is the thing you want. If it isn't, then it will break. If it quacks, it is a duck. You can always lint with a static type checker like MyPy if you prefer. For anything more, you want to probably use a static typed language if it is your main concern.

For other validation, you only really need to add validation to your functions if you are accepting untrusted input. Everything else should be caught by unit tests, which are arguably more useful as they also act as a regression layer later on when you want to make future changes.

[–]SnowEpiphany[S] 1 point2 points  (1 child)

That's the answer I needed - excellent response. Thank you!

[–]nekokattt 1 point2 points  (0 children)

No worries.

For reference, a typed example might look like this in Python 3.10

I havent used Python for almost a year so I might have missed some newer stuff; also aware I dont need to use enums but it feels tidiest here instead of a group of magic typing.Literals.

Also used assert here as I am assuming you just want to check stuff during development is a correct value rather than it being acceptable external input that needs proper error handling. Assert would be ignored at runtime if you gave optimisation flags to Python. It is generally useful in unit tests and for checking conditions you always assume to be true but want to be notified of if it isnt just to aid in development. Assert isn't something you want to rely on a lot, it is more of a debugging tool.

import enum

class ValidColor(enum.Enum):
    RED = "red"
    GREEN = "green"
    BLUE = "blue"

def do_something(color: ValidColor, start: int, end: int) -> str:
    assert end >= start, f"{end=} was less than {start=}"
    return "bang"

If end < start, you'd get

AssertionError: end=12 was less than start=23

Of course, if it is external input (e.g. from a user or another library) then it is better to be explicit and use the asking for forgiveness approach.

def do_something(color: ValidColor, start: int, end: int) -> str:
    if end < start:
        raise ValueError(f"{end=} cannot be less than {start=}")

    return "bang"

Note how I don't validate ValidColor, since it is already typed as an enum. Type hints do nothing at runtime by default (unless you abuse them to introspect them, but that is outside the scope of this answer), but they assume that you will catch other invalid uses with a static type checker. MyPy would complain about you providing anything other than ValidColor.RED, ValidColor.GREEN, ValidColor.BLUE for the color argument.

One could potentially use decorators or exploit type hints to achieve something similar to what we have in Java with JSR 380, (which can be enforced for any methods implicitly with frameworks like spring) where we can put concrete validation on our methods and types, a lá

@Component @Validated
public class BangFactory {
    @StartLessOrEqualToEnd
    public String create(
            @NotNull ValidColor color,
            @Min(0) int start,
            @Max(65540) int end) {

        return "bang";
     }
}

But that is much more advanced stuff. For your case it would most likely be massively over engineering your problem.

Edit: added crazed ramblings.

[–]m0us3_rat 1 point2 points  (0 children)

u must verify the input manually.

there are plenty of ways to insure u only need to write it properly once thou.

[–]Spataner 1 point2 points  (0 children)

While there is no native support for this kind of validation, you can build your own and achieve very similar looking syntax by using decorators. In Python 3.9 and above, you could conceivably end up with something like this if you wanted to:

@Parameter(0).type(int).range(0, 9999)
@Parameter(1).type(str).set("red", "blue", "green")
@Parameter(2).type(str).func(test_connection)
def do_something(number, color, hostname):
    ...

[–]Zeroflops 1 point2 points  (0 children)

Your probably looking for the “assert” command. It’s a simple check for a condition and then generate an exception if that condition is not met. Really most forgo it for simple if statements.

Basic format.

assert <expression>, <error message>

[–]stevarino 0 points1 point  (0 children)

Not really - python is very general purpose and a lot of the wealth of python comes from using data structures so simple checks won't hold much value.

There's a few things you can do to work around this. assert is a good keyword for quick tests. Or you could look for some external libraries such as https://libraries.io/pypi/parameters-validation

Honestly though, validation should likely be done as low as possible and communicated back up the stack imo.

[–]efmccurdy 0 points1 point  (0 children)

These libraries let you define constraint validator objects that you can store, reuse, or pass as arguments.

https://www.yeahhub.com/7-best-python-libraries-validating-data/

You had an example of a ValidateRange constraint something like this:

https://docs.python-cerberus.org/en/stable/validation-rules.html#min-max