7

What is the most "correct", Pythonic way to do user input validation in Python?

I've been using the following:

while True:
    stuff = input("Please enter foo: ")
    try:
        some_test(stuff)
        print("Thanks.")
        break
    except SomeException:
        print("Invalid input.")

Which is nice and readable, I suppose, but I can't help wondering if there isn't some built-in function or something I should be using instead.

eyquem
  • 26,771
  • 7
  • 38
  • 46
henrebotha
  • 1,244
  • 2
  • 14
  • 36
  • 1
    Could you please show more code? – thefourtheye Dec 12 '13 at 10:00
  • Sorry, I accidentally submitted before finishing typing! – henrebotha Dec 12 '13 at 10:01
  • I think what your done (using try~except) is not bad though there are other ways for the same task. I don't heard about the "Pythonic" way.. This kind of task is occurred in all other languages. – GoodGJ Dec 12 '13 at 10:08
  • 1
    Post the `some_test` function, please – inspectorG4dget Dec 12 '13 at 10:08
  • Entirely depends on what you mean by "input validation" (credit card number, IP address, int, float?), and what you want to do when a validation failure occurs. – Lukas Graf Dec 12 '13 at 10:09
  • The some_test function should not be relevant. In one particular situation I am dealing with now, some_test looks as follows: `with smtplib.SMTP_SSL(host=host, port=port) as server: server.login(user, password)` which raises an exception if login is unsuccessful. However, I am asking in general. – henrebotha Dec 12 '13 at 10:10
  • @LukasGraf: What I want to do on validation failure is repeatedly ask the user for input until he complies, lol. – henrebotha Dec 12 '13 at 10:11
  • @InbarRose: Thanks for the link, I actually quite like your answer. – henrebotha Dec 12 '13 at 10:16

3 Answers3

10

I like decorators to separate the checking from the rest of the input handling.

#!/usr/bin/env python

def repeatOnError(*exceptions):
  def checking(function):
    def checked(*args, **kwargs):
      while True:
        try:
          result = function(*args, **kwargs)
        except exceptions as problem:
          print "There was a problem with the input:"
          print problem.__class__.__name__
          print problem
          print "Please repeat!"
        else: 
          return result
    return checked
  return checking

@repeatOnError(ValueError)
def getNumberOfIterations():
  return int(raw_input("Please enter the number of iterations: "))

iterationCounter = getNumberOfIterations()
print "You have chosen", iterationCounter, "iterations."

EDIT:

A decorator is more or less a wrapper for an existing function (or method). It takes the existing function (denoted below its @decorator directive) and returns a "replacement" for it. This replacement in our case calls the original function in a loop and catches any exception happening while doing so. If no exception happens, it just returns the result of the original function.

Alfe
  • 56,346
  • 20
  • 107
  • 159
  • This is also quite good and takes it a step further and helps to generalize it a bit more. Python decorators are a very powerful tool that let you compose functions in a nice way. – James Mills Dec 12 '13 at 10:13
  • @Alfe: Thanks very much for your answer. I am however completely in the dark as to how decorators work, despite fervent Googling, so I'm afraid I don't really understand your answer. :( – henrebotha Dec 12 '13 at 10:14
  • 2
    I added a small explanation for the code, but of course, it would be out of scope to explain decorators here. There are plenty of questions concerning that topic on Stackoverflow. – Alfe Dec 12 '13 at 10:20
  • Thank you. I'm sort of understanding it a bit better now. – henrebotha Dec 12 '13 at 10:22
3

The most Pythonic way to do this kind of validation of "User INput" is to catch an appropriate exception.

Example:

def get_user_input():
    while True:
        try:
            return int(input("Please enter a number: "))
        except ValueError:
            print("Invalid input. Please try again!")

n = get_user_input()
print("Thanks! You entered: {0:d}".format(n))

It's also good practice to allow exceptions occur where they lie and allow them to bubble up rather than hide them so you can clearly see what's going wrong in a Python Traceback.

In this case Validating User Input -- Use Python's Duck Typing and catch the error. i.e: If it acts like a duct, it must be a duck. (If it acts like an int, it must be an int).

James Mills
  • 18,669
  • 3
  • 49
  • 62
0

A bit complicated, but may be interesting:

import re
from sys import exc_info,excepthook
from traceback import format_exc

def condition1(stuff):
    '''
    stuff must be the string of an integer'''
    try:
        i = int(stuff)
        return True
    except:
        return False

def condition2(stuff):
    '''
    stuff is the string of an integer
    but the integer must be in the range(10,30)'''
    return int(stuff) in xrange(10,30)

regx = re.compile('assert *\( *([_a-z\d]+)')
                  
while True:
    try:
        stuff = raw_input("Please enter foo: ")
        assert(condition1(stuff))
        assert (  condition2(stuff))
        print("Thanks.")
        break
    except AssertionError:
        tbs = format_exc(exc_info()[0])
        funky = globals()[regx.search(tbs).group(1)]
        excepthook(exc_info()[0], funky.func_doc, None)

result

Please enter foo: g
AssertionError: 
    stuff must be the string of an integer
Please enter foo: 170
AssertionError: 
    stuff is the string of an integer
    but the integer must be in the range(10,30)
Please enter foo: 15
Thanks.

.

EDIT

I found a way to simplify:

from sys import excepthook

def condition1(stuff):
    '''
    stuff must be the string of an integer'''
    try:
        int(stuff)
        return True
    except:
        return False

def another2(stuff):
    '''
    stuff is the string of an integer
    but the integer must be in the range(10,30)'''
    return int(stuff) in xrange(10,30)

tup = (condition1,another2)

while True:
    try:
        stuff = raw_input("Please enter foo: ")
        for condition in tup:
            assert(condition(stuff))
        print("Thanks.")
        break
    except AssertionError:
        excepthook('AssertionError', condition.func_doc, None)
Community
  • 1
  • 1
eyquem
  • 26,771
  • 7
  • 38
  • 46
  • Interesting indeed, but very complicated for routine tasks, I think. – henrebotha Dec 12 '13 at 16:12
  • What is somewhat horrid is the way I must follow to find the function whose result has caused the Assertion error: traceback object converted to a string, regex searching in this string, passing the group to glabals to find the function object, getting the doc of the function ! I didn't succeed to extract the function or its name more directly from the traceback object. - But the basic idea may be interesting: conditional functions can be added simply without changing the except block. – eyquem Dec 12 '13 at 16:22
  • @henrebotha I got a strong simplification – eyquem Dec 12 '13 at 16:59