유효한 응답을 제공 할 때까지 사용자에게 입력 요청

python validation loops python-3.x user-input


사용자의 입력을 수락하는 프로그램을 작성 중입니다.

#note: Python 2.7 users should use `raw_input`, the equivalent of 3.X's `input`
age = int(input("Please enter your age: "))
if age >= 18: 
    print("You are able to vote in the United States!")
else:
    print("You are not able to vote in the United States.")

사용자가 의미있는 데이터를 입력하는 한 프로그램이 예상대로 작동합니다.

C:\Python\Projects> canyouvote.py
Please enter your age: 23
You are able to vote in the United States!

그러나 사용자가 유효하지 않은 데이터를 입력하면 실패합니다.

C:\Python\Projects> canyouvote.py
Please enter your age: dickety six
Traceback (most recent call last):
  File "canyouvote.py", line 1, in <module>
    age = int(input("Please enter your age: "))
ValueError: invalid literal for int() with base 10: 'dickety six'

충돌하는 대신 프로그램에서 입력을 다시 요청하고 싶습니다. 이처럼 :

C:\Python\Projects> canyouvote.py
Please enter your age: dickety six
Sorry, I didn't understand that.
Please enter your age: 26
You are able to vote in the United States!

중요하지 않은 데이터를 입력 할 때 프로그램이 충돌하지 않고 유효한 입력을 요청하도록하려면 어떻게해야합니까?

이 문맥에서 유효한 int 이지만 무의미한 -1 과 같은 값을 어떻게 거부 할 수 있습니까?





Answer 1 Kevin


이를 수행하는 가장 간단한 방법은 input 메소드를 while 루프에 넣는 것입니다. 입력이 잘못되면 continue 사용하고 만족하면 루프에서 빠져 나옵니다.

입력에서 예외가 발생할 수있는 경우

tryexcept 를 사용하여 사용자가 구문 분석 할 수없는 데이터를 입력하는시기를 감지하십시오.

while True:
    try:
        # Note: Python 2.x users should use raw_input, the equivalent of 3.x's input
        age = int(input("Please enter your age: "))
    except ValueError:
        print("Sorry, I didn't understand that.")
        #better try again... Return to the start of the loop
        continue
    else:
        #age was successfully parsed!
        #we're ready to exit the loop.
        break
if age >= 18: 
    print("You are able to vote in the United States!")
else:
    print("You are not able to vote in the United States.")

자신의 유효성 검사 규칙 구현

파이썬이 성공적으로 구문 분석 할 수있는 값을 거부하려면 고유 한 유효성 검사 논리를 추가하면됩니다.

while True:
    data = input("Please enter a loud message (must be all caps): ")
    if not data.isupper():
        print("Sorry, your response was not loud enough.")
        continue
    else:
        #we're happy with the value given.
        #we're ready to exit the loop.
        break

while True:
    data = input("Pick an answer from A to D:")
    if data.lower() not in ('a', 'b', 'c', 'd'):
        print("Not an appropriate choice.")
    else:
        break

예외 처리 및 사용자 지정 유효성 검사 결합

위의 기술은 모두 하나의 루프로 결합 될 수 있습니다.

while True:
    try:
        age = int(input("Please enter your age: "))
    except ValueError:
        print("Sorry, I didn't understand that.")
        continue

    if age < 0:
        print("Sorry, your response must not be negative.")
        continue
    else:
        #age was successfully parsed, and we're happy with its value.
        #we're ready to exit the loop.
        break
if age >= 18: 
    print("You are able to vote in the United States!")
else:
    print("You are not able to vote in the United States.")

함수에서 모두 캡슐화

사용자에게 많은 다른 값을 요청해야하는 경우이 코드를 함수에 넣는 것이 유용 할 수 있으므로 매번 다시 입력하지 않아도됩니다.

def get_non_negative_int(prompt):
    while True:
        try:
            value = int(input(prompt))
        except ValueError:
            print("Sorry, I didn't understand that.")
            continue

        if value < 0:
            print("Sorry, your response must not be negative.")
            continue
        else:
            break
    return value

age = get_non_negative_int("Please enter your age: ")
kids = get_non_negative_int("Please enter the number of children you have: ")
salary = get_non_negative_int("Please enter your yearly earnings, in dollars: ")

함께 모아서

이 아이디어를 확장하여 매우 일반적인 입력 기능을 만들 수 있습니다.

def sanitised_input(prompt, type_=None, min_=None, max_=None, range_=None):
    if min_ is not None and max_ is not None and max_ < min_:
        raise ValueError("min_ must be less than or equal to max_.")
    while True:
        ui = input(prompt)
        if type_ is not None:
            try:
                ui = type_(ui)
            except ValueError:
                print("Input type must be {0}.".format(type_.__name__))
                continue
        if max_ is not None and ui > max_:
            print("Input must be less than or equal to {0}.".format(max_))
        elif min_ is not None and ui < min_:
            print("Input must be greater than or equal to {0}.".format(min_))
        elif range_ is not None and ui not in range_:
            if isinstance(range_, range):
                template = "Input must be between {0.start} and {0.stop}."
                print(template.format(range_))
            else:
                template = "Input must be {0}."
                if len(range_) == 1:
                    print(template.format(*range_))
                else:
                    print(template.format(" or ".join((", ".join(map(str,
                                                                     range_[:-1])),
                                                       str(range_[-1])))))
        else:
            return ui

다음과 같은 사용법으로 :

age = sanitised_input("Enter your age: ", int, 1, 101)
answer = sanitised_input("Enter your answer: ", str.lower, range_=('a', 'b', 'c', 'd'))

일반적인 함정과 왜 피해야 하는가

중복 input 명령문의 중복 사용

이 방법은 효과가 있지만 일반적으로 스타일이 좋지 않습니다.

data = input("Please enter a loud message (must be all caps): ")
while not data.isupper():
    print("Sorry, your response was not loud enough.")
    data = input("Please enter a loud message (must be all caps): ")

while True 방법보다 짧기 때문에 처음에는 매력적으로 보일 수 있지만 소프트웨어 개발의 Do n't Repeat Yourself 원칙을 위반합니다. 이것은 시스템의 버그 가능성을 증가시킵니다. inputraw_input 으로 변경하여 2.7로 백 포트하고 싶지만 실수로 위의 첫 번째 input 만 변경하려면 어떻게해야합니까? 그냥 일어나기를 기다리는 SyntaxError 입니다.

재귀가 스택을 날려 버릴 것입니다

재귀에 대해 방금 배운 경우 get_non_negative_int 에서 재귀를 사용하여 while 루프를 처리 할 수 ​​있습니다.

def get_non_negative_int(prompt):
    try:
        value = int(input(prompt))
    except ValueError:
        print("Sorry, I didn't understand that.")
        return get_non_negative_int(prompt)

    if value < 0:
        print("Sorry, your response must not be negative.")
        return get_non_negative_int(prompt)
    else:
        return value

대부분의 경우 제대로 작동하는 것으로 보이지만 사용자가 유효하지 않은 데이터를 충분히 입력하면 스크립트는 RuntimeError: maximum recursion depth exceeded 종료됩니다 . 최대 재귀 깊이가 초과되었습니다 . "어리 석음으로 1000 번 실수하지 않을 것"이라고 생각할 수도 있지만, 바보의 독창성을 과소 평가하는 것입니다!




Answer 2 Steven Stip


while True 를 수행 한 다음이 루프에서 벗어나 왜 while 문에 요구 사항을 넣을 수 있습니까?

age = None
while age is None:
    input_value = input("Please enter your age: ")
    try:
        # try and convert the string input to a number
        age = int(input_value)
    except ValueError:
        # tell the user off
        print("{input} is not a number, please enter a number only".format(input=input_value))
if age >= 18:
    print("You are able to vote in the United States!")
else:
    print("You are not able to vote in the United States.")

결과는 다음과 같습니다.

Please enter your age: *potato*
potato is not a number, please enter a number only
Please enter your age: *5*
You are not able to vote in the United States.

이것은 나이가 의미가없는 가치를 가지지 않기 때문에 작동하며 코드는 "비즈니스 프로세스"의 논리를 따릅니다.




Answer 3 aaveg


받아 들여진 대답은 훌륭합니다. 또한이 문제에 대한 빠른 해킹을 공유하고 싶습니다. (이는 부정적인 연령 문제도 처리합니다.)

f=lambda age: (age.isdigit() and ((int(age)>=18  and "Can vote" ) or "Cannot vote")) or \
f(input("invalid input. Try again\nPlease enter your age: "))
print(f(input("Please enter your age: ")))

PS이 코드는 python 3.x 용입니다.




Answer 4 cat


그래서 최근에 이와 비슷한 것을 엉망으로 만들었고 논리적으로 검사하기 전에 정크를 거부하는 입력을 얻는 방법을 사용하는 다음 솔루션을 생각해 냈습니다.

read_single_keypress() 의례 https://stackoverflow.com/a/6599441/4532996

def read_single_keypress() -> str:
    """Waits for a single keypress on stdin.
    -- from :: https://stackoverflow.com/a/6599441/4532996
    """

    import termios, fcntl, sys, os
    fd = sys.stdin.fileno()
    # save old state
    flags_save = fcntl.fcntl(fd, fcntl.F_GETFL)
    attrs_save = termios.tcgetattr(fd)
    # make raw - the way to do this comes from the termios(3) man page.
    attrs = list(attrs_save) # copy the stored version to update
    # iflag
    attrs[0] &= ~(termios.IGNBRK | termios.BRKINT | termios.PARMRK
                  | termios.ISTRIP | termios.INLCR | termios. IGNCR
                  | termios.ICRNL | termios.IXON )
    # oflag
    attrs[1] &= ~termios.OPOST
    # cflag
    attrs[2] &= ~(termios.CSIZE | termios. PARENB)
    attrs[2] |= termios.CS8
    # lflag
    attrs[3] &= ~(termios.ECHONL | termios.ECHO | termios.ICANON
                  | termios.ISIG | termios.IEXTEN)
    termios.tcsetattr(fd, termios.TCSANOW, attrs)
    # turn off non-blocking
    fcntl.fcntl(fd, fcntl.F_SETFL, flags_save & ~os.O_NONBLOCK)
    # read a single keystroke
    try:
        ret = sys.stdin.read(1) # returns a single character
    except KeyboardInterrupt:
        ret = 0
    finally:
        # restore old state
        termios.tcsetattr(fd, termios.TCSAFLUSH, attrs_save)
        fcntl.fcntl(fd, fcntl.F_SETFL, flags_save)
    return ret

def until_not_multi(chars) -> str:
    """read stdin until !(chars)"""
    import sys
    chars = list(chars)
    y = ""
    sys.stdout.flush()
    while True:
        i = read_single_keypress()
        _ = sys.stdout.write(i)
        sys.stdout.flush()
        if i not in chars:
            break
        y += i
    return y

def _can_you_vote() -> str:
    """a practical example:
    test if a user can vote based purely on keypresses"""
    print("can you vote? age : ", end="")
    x = int("0" + until_not_multi("0123456789"))
    if not x:
        print("\nsorry, age can only consist of digits.")
        return
    print("your age is", x, "\nYou can vote!" if x >= 18 else "Sorry! you can't vote")

_can_you_vote()

여기 에서 전체 모듈을 찾을 수 있습니다.

Example:

$ ./input_constrain.py
can you vote? age : a
sorry, age can only consist of digits.
$ ./input_constrain.py 
can you vote? age : 23<RETURN>
your age is 23
You can vote!
$ _

이 구현의 특성은 숫자가 아닌 것을 읽 자마자 stdin이 닫히는 것입니다. 나는 이후 a enter 키를 누르지 않았지만 숫자를 따라야했습니다.

이것을 같은 모듈의 thismany() 함수와 병합하여 3 자리 숫자 만 허용 할 수 있습니다.




Answer 5 Georgy


기능적 접근 방식 또는 " 루프가없는 것처럼 보입니다! ":

from itertools import chain, repeat

prompts = chain(["Enter a number: "], repeat("Not a number! Try again: "))
replies = map(input, prompts)
valid_response = next(filter(str.isdigit, replies))
print(valid_response)
Enter a number:  a
Not a number! Try again:  b
Not a number! Try again:  1
1

또는 다른 답변에서와 같이 "잘못된 입력"메시지를 입력 프롬프트와 분리하려면 다음을 수행하십시오.

prompt_msg = "Enter a number: "
bad_input_msg = "Sorry, I didn't understand that."
prompts = chain([prompt_msg], repeat('\n'.join([bad_input_msg, prompt_msg])))
replies = map(input, prompts)
valid_response = next(filter(str.isdigit, replies))
print(valid_response)
Enter a number:  a
Sorry, I didn't understand that.
Enter a number:  b
Sorry, I didn't understand that.
Enter a number:  1
1

어떻게 작동합니까?

  1. prompts = chain(["Enter a number: "], repeat("Not a number! Try again: "))
    itertools.chain itertools.repeat 의이 조합은 "Enter a number: " 한 번, "Not a number! Try again: " 라는 무한한 횟수의 문자열을 생성하는 반복자를 만듭니다.
    for prompt in prompts:
        print(prompt)
    Enter a number: 
    Not a number! Try again: 
    Not a number! Try again: 
    Not a number! Try again: 
    # ... and so on
  2. replies = map(input, prompts) -여기서 map 은 이전 단계의 모든 prompts 문자열을 input 함수에 적용합니다. 예 :
    for reply in replies:
        print(reply)
    Enter a number:  a
    a
    Not a number! Try again:  1
    1
    Not a number! Try again:  it doesn't care now
    it doesn't care now
    # and so on...
  3. filter str.isdigit 를 사용하여 숫자 만 포함 된 문자열을 걸러냅니다.
    only_digits = filter(str.isdigit, replies)
    for reply in only_digits:
        print(reply)
    Enter a number:  a
    Not a number! Try again:  1
    1
    Not a number! Try again:  2
    2
    Not a number! Try again:  b
    Not a number! Try again: # and so on...
    그리고 첫 번째 숫자 전용 문자열 만 얻으려면 next 사용하십시오.

다른 유효성 검사 규칙 :

  1. 문자열 메서드 : 물론 str.isalpha 와 같은 다른 문자열 메서드를 사용하여 알파벳 문자열 만 가져 오거나 str.isupper 는 대문자 만 사용할 수 있습니다. 전체 목록은 문서 를 참조하십시오.

  2. 회원 테스트 :
    여러 가지 방법으로 수행 할 수 있습니다. 그중 하나는 __contains__ 메소드를 사용하는 것입니다.

    from itertools import chain, repeat
    
    fruits = {'apple', 'orange', 'peach'}
    prompts = chain(["Enter a fruit: "], repeat("I don't know this one! Try again: "))
    replies = map(input, prompts)
    valid_response = next(filter(fruits.__contains__, replies))
    print(valid_response)
    Enter a fruit:  1
    I don't know this one! Try again:  foo
    I don't know this one! Try again:  apple
    apple
  3. 숫자 비교 :
    여기서 사용할 수있는 유용한 비교 방법이 있습니다. 예를 들어 __lt__ ( < )의 경우 :

    from itertools import chain, repeat
    
    prompts = chain(["Enter a positive number:"], repeat("I need a positive number! Try again:"))
    replies = map(input, prompts)
    numeric_strings = filter(str.isnumeric, replies)
    numbers = map(float, numeric_strings)
    is_positive = (0.).__lt__
    valid_response = next(filter(is_positive, numbers))
    print(valid_response)
    Enter a positive number: a
    I need a positive number! Try again: -5
    I need a positive number! Try again: 0
    I need a positive number! Try again: 5
    5.0

    또는 dunder 메소드 (dunder = double-underscore)를 사용하지 않으려는 경우 언제든지 고유 한 함수를 정의하거나 operator 모듈에서 해당 함수를 사용할 수 있습니다.

  4. 경로 존재 :
    여기서는 pathlib 라이브러리와 해당 Path.exists 메소드를 사용할 수 있습니다.

    from itertools import chain, repeat
    from pathlib import Path
    
    prompts = chain(["Enter a path: "], repeat("This path doesn't exist! Try again: "))
    replies = map(input, prompts)
    paths = map(Path, replies)
    valid_response = next(filter(Path.exists, paths))
    print(valid_response)
    Enter a path:  a b c
    This path doesn't exist! Try again:  1
    This path doesn't exist! Try again:  existing_file.txt
    existing_file.txt

시도 횟수 제한 :

사용자에게 무언가를 요청하여 고문하고 싶지 않은 경우 itertools.repeat 호출에서 제한을 지정할 수 있습니다. next 기능에 기본값을 제공하는 것과 결합 할 수 있습니다.

from itertools import chain, repeat

prompts = chain(["Enter a number:"], repeat("Not a number! Try again:", 2))
replies = map(input, prompts)
valid_response = next(filter(str.isdigit, replies), None)
print("You've failed miserably!" if valid_response is None else 'Well done!')
Enter a number: a
Not a number! Try again: b
Not a number! Try again: c
You've failed miserably!

전처리 입력 데이터 :

때때로 사용자가 실수로 입력 한 값을 입력하거나 문자열의 시작 또는 끝에 공백이있는 경우 입력을 거부하고 싶지 않습니다. 이러한 간단한 실수를 고려하기 위해 str.lower str.strip 메소드를 적용하여 입력 데이터를 사전 처리 할 수 ​​있습니다. 예를 들어 멤버쉽 테스트의 경우 코드는 다음과 같습니다.

from itertools import chain, repeat

fruits = {'apple', 'orange', 'peach'}
prompts = chain(["Enter a fruit: "], repeat("I don't know this one! Try again: "))
replies = map(input, prompts)
lowercased_replies = map(str.lower, replies)
stripped_replies = map(str.strip, lowercased_replies)
valid_response = next(filter(fruits.__contains__, stripped_replies))
print(valid_response)
Enter a fruit:  duck
I don't know this one! Try again:     Orange
orange

전처리에 사용할 함수가 많은 경우 함수 구성을 수행하는 함수 를 사용하는 것이 더 쉬울 수 있습니다. 예를 들어 여기 에서 하나를 사용 하십시오 .

from itertools import chain, repeat

from lz.functional import compose

fruits = {'apple', 'orange', 'peach'}
prompts = chain(["Enter a fruit: "], repeat("I don't know this one! Try again: "))
replies = map(input, prompts)
process = compose(str.strip, str.lower)  # you can add more functions here
processed_replies = map(process, replies)
valid_response = next(filter(fruits.__contains__, processed_replies))
print(valid_response)
Enter a fruit:  potato
I don't know this one! Try again:   PEACH
peach

유효성 검사 규칙 결합 :

예를 들어, 프로그램이 1에서 120 사이의 연령을 요구하면 간단한 filter 추가 할 수 있습니다.

from itertools import chain, repeat

prompt_msg = "Enter your age (1-120): "
bad_input_msg = "Wrong input."
prompts = chain([prompt_msg], repeat('\n'.join([bad_input_msg, prompt_msg])))
replies = map(input, prompts)
numeric_replies = filter(str.isdigit, replies)
ages = map(int, numeric_replies)
positive_ages = filter((0).__lt__, ages)
not_too_big_ages = filter((120).__ge__, positive_ages)
valid_response = next(not_too_big_ages)
print(valid_response)

그러나 규칙이 많은 경우 논리적 결합을 수행하는 함수를 구현하는 것이 좋습니다. 다음 예제에서는 여기 에서 준비된 것을 사용합니다.

from functools import partial
from itertools import chain, repeat

from lz.logical import conjoin


def is_one_letter(string: str) -> bool:
    return len(string) == 1


rules = [str.isalpha, str.isupper, is_one_letter, 'C'.__le__, 'P'.__ge__]

prompt_msg = "Enter a letter (C-P): "
bad_input_msg = "Wrong input."
prompts = chain([prompt_msg], repeat('\n'.join([bad_input_msg, prompt_msg])))
replies = map(input, prompts)
valid_response = next(filter(conjoin(*rules), replies))
print(valid_response)
Enter a letter (C-P):  5
Wrong input.
Enter a letter (C-P):  f
Wrong input.
Enter a letter (C-P):  CDE
Wrong input.
Enter a letter (C-P):  Q
Wrong input.
Enter a letter (C-P):  N
N

불행히도 누군가가 실패한 각 사례마다 맞춤 메시지가 필요한 경우에는 기능적인 방법이 없습니다. 또는 적어도 하나를 찾을 수 없습니다.