The "loop and a half"

Chris Angelico rosuav at gmail.com
Tue Oct 3 22:40:45 EDT 2017


On Wed, Oct 4, 2017 at 1:17 PM, Larry Hudson via Python-list
<python-list at python.org> wrote:
> def get_num(prmt, lo=None, hi=None, dflt=None, flt=False, abrt=False):
>     """Get a number from the console
>
>     Parameters:
>         prmt:   The prompt to be used with the input function, required.
>         lo:     The minimum permissible value, or None if no minimum (the
> default)
>         hi:     The maximum permissible value, or None if no maximum (the
> default)
>         dflt:   Default value to return with empty input, None is no default
> value
>         flt:    If True, accepts and returns floats
>                 If False, accepts and returns integers (the default)
>         abrt:   If True empty input aborts and returns None
>                 If False (the default) empty input is handled as invalid
> data
>
>     Invalid input or out-of-range values are not accepted and a warning
> message
>         is displayed.  It will then repeat the input prompt for a new value.
>     Note:  If the dflt parameter is given, its data type is not checked and
>         could be anything, and could also be used as an abort signal.
>     """
>
>     while True:
>         val = input(prmt).strip()
>         if not val:
>             if abrt:
>                 return None
>             if dflt is not None:
>                 return dflt
>         try:
>             num = float(val) if flt else int(val)
>         except ValueError:
>             print('Invalid input, try again')
>             continue
>         #   We have a valid number here, check for in-range
>         if (lo is None or num >= lo) and (hi is None or num <= hi):
>             return num
>         print('Number is out of range')

You know, you don't HAVE to economize on letters. It's okay to call
your parameters "prompt" instead of "prmt". Remember, that's part of
your API.

I'd whitewash the bikeshed a slightly different colour, personally.

_SENTINEL = object()
def get_number(prompt, *, min=None, max=None, default=_SENTINEL, type=int):

Broadly the same as you have (modulo variable names), but there's no
need for the boolean 'abrt', since setting default to None will have
the same effect - hence the dedicated sentinel object to represent "no
default". (I also mandated keyword args, since that's how you're using
them anyway.) The one real change I'd make is to replace the boolean
flag "flt" with a type, so you now say:

x = get_number("Number (enter to terminate):  ", type=float)

It reads more cleanly than "flt=True", and you don't have to look up
any docs to grok it; plus, if you ever need to, you can use
"type=Decimal" or something. It's simpler inside the code too - "num =
type(val)". If you don't like shadowing the built-in name 'type', feel
free to call this something else, but IMO it's okay to shadow here.

ChrisA



More information about the Python-list mailing list