9

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.

8
  • 1
    Could you please show more code? Commented Dec 12, 2013 at 10:00
  • Sorry, I accidentally submitted before finishing typing! Commented Dec 12, 2013 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. Commented Dec 12, 2013 at 10:08
  • 1
    Post the some_test function, please Commented Dec 12, 2013 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. Commented Dec 12, 2013 at 10:09

3 Answers 3

11

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.

Sign up to request clarification or add additional context in comments.

4 Comments

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.
@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. :(
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.
Thank you. I'm sort of understanding it a bit better now.
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).

2 Comments

In other words, exactly what I'm doing. :) Thanks James
Precisely :) I just thought I'd expand on it and talk about the "Pythonic way" and the why(s) :)
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) 

3 Comments

Interesting indeed, but very complicated for routine tasks, I think.
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.
@henrebotha I got a strong simplification

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.