СУХОЙ контроль ввода в Python

Скажем, я хочу написать очень простую программу на Python, чтобы запрашивать у пользователя его личную информацию и возвращать ее обратно в «красивом» формате.

def get_info():
    first = input("enter your first name: ")
    while not first.isalpha():
        first = input("enter your first name: ")

    last = input("enter your last name: ")
    while not last.isalpha():
        last = input("enter your last name: ")

    age = input("enter your age: ")
    while not age.isnumeric():
        age = input("enter your age: ")
    age = int(age)

    has_pet = input("do you have a pet?: ").lower()
    while has_pet not in ["yes", "no", "y", "n"]:
        has_pet = input("do you have a pet?: ").lower()

    if "y" in has_pet:
        has_pet = True
    else:
        has_pet = False
    
    return [first, last, age, has_pet]

def print_info(info):
    first, last, age, has_pet = info
    print(f"Name: {first} {last}")
    print(f"Age: {age}")
    print(f"Has pet: {has_pet}")

print_info(get_info())

Ясно, что я нарушал DRY несколько раз.
(1) Мне просто бросить input() функции и использовать аргументы (с sys.argv)?
(2) Причина, по которой я не могу использовать рекурсию, заключается в том, что мне пришлось бы снова запрашивать у пользователя всю их информацию, если он ошибся, например, отвечая на вопрос о домашнем животном. Есть ли другой способ очистить этот код, не жертвуя скоростью / ресурсами?

3 ответа
3

Да отказаться input(), который обычно является менее мощным и гибким выбором. Вот приблизительное значение вашего кода, используя
argparse. Обратите внимание на несколько вещей: (1) очень мало повторений в нашем коде; (2) гораздо меньше алгоритмического кода, что означает меньше шансов на ошибку; (3) большая простота в целом и, следовательно, более высокая читаемость; (4) хорошая проверка и справочные сообщения сразу после установки; (5) множество других возможностей (см. Документацию библиотеки); (6) меньше хлопот для пользователей при повторном запуске кода или в любом автоматическом контексте; и (7) меньше хлопот при разработке кода по той же причине.

# Usage examples:

$ python demo.py George Washington 289 --pet
Namespace(age=289, first_name="George", last_name="Washington", pet=True)

# The code:

import argparse
import sys

def main(args):
    ap, opts = parse_args(args)
    print(opts)

def parse_args(args):
    ap = argparse.ArgumentParser()
    ap.add_argument('first_name', type = personal_name)
    ap.add_argument('last_name', type = personal_name)
    ap.add_argument('age', type = int)
    ap.add_argument('--pet', action = 'store_true')
    opts = ap.parse_args(args)
    return (ap, opts)

def personal_name(x):
    if x.isalpha():
        return x
    else:
        raise ValueError(f'Invalid name: {x}')

if __name__ == '__main__':
    main(sys.argv[1:])

Но если вы не можете или не хотите идти по этому пути, подход «ролл-сам» выглядит аналогичным по духу: нам нужна универсальная функция для получения входных данных; он должен принять какой-то текст или метку, чтобы сообщить пользователю, о чем мы просим; и ему нужен конвертер / валидатор для проверки ответа. Вот примерный набросок новых или других деталей:

def main(args):
    d = dict(
        first = get_input(label="first name", convert = personal_name),
        last = get_input(label="last name", convert = personal_name),
        age = get_input(label="age", convert = int),
        pet = get_input(label="pet status [y/n]", convert = yesno),
    )
    print(d)

def yesno(x):
    if x in ('yes', 'y'):
        return True
    elif x in ('no', 'n'):
        return False
    else:
        raise ValueError(f'Invalid yes-no: {x}')

def get_input(label, convert):
    while True:
        reply = input(f'Enter {label}: ')
        try:
            return convert(reply)
        except (ValueError, TypeError):
            print('Invalid reply')

    Вы можете абстрагировать проверку символа в функции и сделать один оператор печати:

    def get_info():
        def valid_check(alpha, message):
            """
            Assumes:
                alpha is a boolean discerning whether to check for alpha or numeric
                message is an input request string
            Returns:
                validated input, in string or int type according to alpha
            """
            assert type(alpha) == bool, "alpha should be boolean"
            if alpha:
                inp = input("%s: " % message)
                while not inp.isalpha():
                    inp = input("%s: " % message)
                return inp
            else:
                inp = input("%s: " % message)
                while not inp.isnumeric():
                    inp = input("%s: " % message)
                return int(inp)
    
        first = valid_check(True,"enter your first name")
        last = valid_check(True,"enter your last name")
        age = valid_check(False,"enter your age")
        
        has_pet = input("do you have a pet?: ").lower()
        while has_pet not in ["yes", "no", "y", "n"]:
            has_pet = input("do you have a pet?: ").lower()
    
        if "y" in has_pet:
            has_pet = True
        else:
            has_pet = False
        
        return [first, last, age, has_pet]
    
    def print_info(info):
        first, last, age, has_pet = info
        print(f"Name: {first} {last}nAge: {age}nHas pet: {has_pet}")
    
    print_info(get_info())
    

    • 1

      Можете ли вы объяснить внесенные вами изменения — как и почему внесенные вами изменения лучше, чем код OP.

      — Пейлонрайз

    Я не вижу особых причин использовать sys.argv или начальник argparse. Оба кажутся дополнительной сложностью без особой пользы.

    Вы можете СУШИТЬ свой код двумя способами:

    • Сделать цикл while

      Python не имеет циклов do while, но мы можем имитировать их, используя while True: и заканчивая блок if вырваться из петли.

      При этом мы можем использовать одну строку, а не две.

      while True:
          first = input("enter your first name: ")
          while first.isalpha():
              break
      
    • Пользовательский ввод

      Мы можем расширить input чтобы иметь желаемую функциональность.

      Мы видим, что ваши петли состоят из двух похожих частей:

      1. Преобразуйте значение.
        int(age) и str.lower()

      2. Подтвердите значение.
        str.isalpha()

      Таким образом, мы можем создать функцию, которая будет делать все, что делают ваши циклы while.

      def super_input(prompt, *, transform=lambda v: v, validate=lambda v: True, input=input):
          while True:
              try:
                  value = transform(input(prompt))
              except ValueError:
                  pass
              else:
                  if validate(value):
                      return value
      
    def get_info():
        first = super_input("enter your first name: ", validate=str.isalpha)
        last = super_input("enter your last name: ", validate=str.isalpha)
        age = super_input("enter your age: ", transform=int)
        has_pet = super_input(
            "do you have a pet?: ",
            transform=str.lower,
            validate=lambda v: v in ["yes", "no", "y", "n"]
        )
        return first, last, age, has_pet.startswith("y")
    

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *