Скажем, я хочу написать очень простую программу на 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 ответа
Да отказаться 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чтобы иметь желаемую функциональность.Мы видим, что ваши петли состоят из двух похожих частей:
Преобразуйте значение.
int(age)иstr.lower()Подтвердите значение.
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")
