В настоящее время я изучаю Python, работая над Автоматизируйте скучную работу с Python. Этот проект от Глава 8 который ориентирован на проверку ввода с помощью модуля PyInputPlus. Когда я заканчиваю, мне нравится искать проблему и сравнивать ее с решениями других, чтобы увидеть, что я мог бы сделать лучше. Однако я могу найти только пару ссылок на эту проблему. Вот в чем проблема:
Изготовитель сэндвичей
Напишите программу, которая запрашивает у пользователей их предпочтения по сэндвичу. Программа должна использовать PyInputPlus, чтобы убедиться, что они вводят допустимые данные, например:
- С помощью
inputMenu()
для сорта хлеба: пшеничный, белый или на закваске.- С помощью
inputMenu()
для протеина: курица, индейка, ветчина или тофу.- С помощью
inputYesNo()
спросить, не хотят ли они сыра. Если да, используяinputMenu()
попросить сыр: чеддер, швейцарский или моцарелла.- С помощью
inputYesNo()
спросить, хотят ли они майонеза, горчицы, салата или помидоров.- С помощью
inputInt()
спросить, сколько бутербродов они хотят. Убедитесь, что это число равно 1 или больше.Придумайте цены для каждого из этих вариантов, и пусть ваша программа отображает общую стоимость после того, как пользователь вводит свой выбор.
При этом я хотел бы услышать, что я могу сделать лучше и как я могу улучшить этот код:
import pyinputplus as pyip
# store a dictionary of ingredients and their respective prices
optionPrices = {'white' : 2.00,
'wheat' : 2.50,
'sour dough' : 3.00,
'chicken' : 2.50,
'turkey' : 2.25,
'ham' : 1.75,
'tofu' : 4.00,
'cheddar' : 1.00,
'swiss' : 1.25,
'mozzarella' : 2.00,
'mayo' : 0.25,
'mustard' : 0.25,
'lettuce' : 0.30,
'tomato' : 0.50
}
customerOrder = [] # a list to store the current order
extras = ['mayo', 'mustard', 'lettuce', 'tomato']
sandwichTotal = 0.0
# ask the user for bread choice and append to order list
breadChoice = pyip.inputMenu(['white', 'wheat', 'sour dough'], 'Please choose your bread:n', lettered=True)
customerOrder.append(breadChoice)
# ask the user for protein choice and append to order list
proteinChoice = pyip.inputMenu(['chicken', 'turkey', 'ham', 'tofu'], 'Please choose your protein:n', lettered=True)
customerOrder.append(proteinChoice)
# ask if the user wants cheese, and if so, record cheese choice
cheeseResponse = pyip.inputYesNo('Would you like cheese?n')
if cheeseResponse == 'yes':
cheeseChoice = pyip.inputMenu(['cheddar', 'swiss', 'mozzarella'], 'Please choose your cheese:n', lettered=True)
customerOrder.append(cheeseChoice)
else:
cheeseChoice=""
# loop through 'extras' and ask if customer wants each one. If so, append it the order
choice=""
for i in extras:
choice = pyip.inputYesNo('Would you like ' + i +'?n')
if choice == 'yes':
customerOrder.append(i)
else:
choice=""
# get the number of sandwiches from the customer
numSandwiches = pyip.inputInt('How many sandwiches would you like?n', min=1)
print('nYour order: ')
# check if the item exists in the options, and get the price for each sandwich
for item in customerOrder:
if item in optionPrices.keys():
sandwichTotal += optionPrices.get(item)
print('t' + item + ' - $' + str(optionPrices.get(item)))
print('Total for your sandwich: $' + str('{:0.2f}'.format(sandwichTotal))) # per sandwhich total
print('Total for your order: (' + str(numSandwiches) + ' sandwiches @ $' +
str('{:0.2f}'.format(sandwichTotal)) + ' each): ')
print('$' + str('{:0.2f}'.format(sandwichTotal * numSandwiches))) # give the total price of sandwiches
Я знаю, что, вероятно, есть много вещей, которые я мог бы сделать лучше для оптимизации или облегчения чтения кода, но есть некоторые вещи, которым я просто еще не научился. Я понимаю, что это, вероятно, не самый эффективный способ решения этой проблемы, и поэтому я хотел бы получить обратную связь.
2 ответа
PEP8
«PEP 8 — «Руководство по стилю кода Python» содержит множество рекомендаций по написанию программ для максимального понимания между программистами.
Имена переменных
Тот, который вы нарушаете больше всего, относится к именованию переменных. PEP8 рекомендует snake_case
для всех переменных. Так customerOrder
должно быть customer_order
, sandwichTotal
должно быть sandwich_total
, так далее.
Константы
Константы, которые никогда не меняются, следует называть с использованием UPPERCASE
или UPPERCASE_WITH_UNDERSCORES
. Следовательно, optionPrices
должно быть OPTION_PRICES
.
Белое пространство
PEP-8 рекомендует не допускать пробелов между клавишей дикции и :
.
Инкапсулировать данные в контейнеры
optionPrices
содержит хлеб, белки, сыры и другие добавки. Похоже, что это слишком много набивки — ну, начинки — в одну емкость.
Я мог бы написать:
BREAD_PRICE = {'white':2.00, 'wheat': 2.50, 'sour dough': 3.00}
PROTEIN_PRICE = {'chicken': 2.50, 'turkey': 2.25, 'ham': 1.75, 'tofu': 4.00}
CHEESE_PRICE = {'cheddar': 1.00, 'swiss' : 1.25, 'mozzarella' : 2.00}
EXTRA_PRICE = {'mayo': 0.25, 'mustard': 0.25, 'lettuce': 0.30, 'tomato': 0.50}
С вариантами хлеба, протеина и сыра теперь вы можете написать общую функцию:
def get_sandwich_choice(category, options):
prompt = "Please choose your " + category + ":n"
choices = list(options.keys())
choice = pyip.inputMenu(choices, prompt, lettered=True)
return choice
bread_choice = get_sandwich_choice('bread', BREAD_PRICE)
protein_choice = get_sandwich_choice('protein', PROTEIN_PRICE)
...
Контейнеры с ценами — это действительно одни и те же вещи, только группами. Так что вместо этого можно …
PRICE = {'bread': {'white':2.00, 'wheat': 2.50, 'sour dough': 3.00},
'protein': {'chicken': 2.50, 'turkey': 2.25, 'ham': 1.75, 'tofu': 4.00},
'cheese': {'cheddar': 1.00, 'swiss' : 1.25, 'mozzarella' : 2.00},
'extras': {'mayo': 0.25, 'mustard': 0.25, 'lettuce': 0.30, 'tomato': 0.50},
}
и варианты выбора можно было получить с помощью:
bread_choice = get_sandwich_choice('bread', PRICE['bread'])
protein_choice = get_sandwich_choice('protein', PRICE['protein'])
...
Вы можете расширить его, добавив больше данных. bread
выбор, protein
это выбор, cheese
необязательный выбор, extras
являются необязательными. В зависимости от типа категории вы можете вызывать соответствующие функции пользовательского ввода. Приглашение для каждой категории можно настроить и так далее. Чтобы добавить эти дополнительные данные, я бы посмотрел на @dataclass
.
Все вещи в customerOrder
тоже самое? Хлеб по-своему уникален; вам понадобится два ломтика для бутерброда. У статистов в конечном итоге могут быть варианты, такие как «легкий майонез» или «тяжелый прием горчицы», когда вы не можете попросить дополнительный хлеб. Так что, возможно, имеет смысл держать их отдельно. bread_choice
, protein_choice
, cheese_choice
, и список extras
. Это тоже помогает нам в стоимости, так как цены указаны в разных контейнерах.
def sandwich_cost(bread, protein, cheese, extras):
cost = PRICE['bread'][bread] + PRICE['protein'][protein]
if cheese:
cost += PRICE['cheese'][cheese]
for extra in extras:
cost += PRICE['extra'][extra]
sandwich_total = sandwich_cost(bread_choice, protein_choice, cheese_choice, extras)
Опять же @dataclass
может пригодиться при строительстве сэндвич-объекта.
Имея отдельную функцию для вычисления стоимости сэндвича, вы делаете свой код тестируемым. Например, вы можете проверить:
assert sandwich_cost('white', 'tofu', 'cheddar', ['tomato']) == 7.50
assert sandwich_cost('white', 'tofu', '', []) == 6.00
Смотреть в unittest
для лучших способов написания тестового кода.
Главный гвардеец
Это хорошая привычка переносить ваш код в функции и вызывать его изнутри «основной защиты» в конце кода. Например)
import pyinputplus as pyip
... helper functions here ...
def main():
... code here ...
if __name__ == '__main__':
main()
Резюме
Множество способов улучшить этот код. Я коснулся нескольких. Поэкспериментируйте, и, когда у вас что-то получится, задайте новый вопрос для получения дополнительных рекомендаций.
Ненужные else-утверждения
В
if cheeseResponse == 'yes':
cheeseChoice = pyip.inputMenu(['cheddar', 'swiss', 'mozzarella'], 'Please choose your cheese:n', lettered=True)
customerOrder.append(cheeseChoice)
else:
cheeseChoice=""
последний оператор else не требуется. cheeseChoice
используется только когда cheeseResponse
является yes
. Следовательно, нет необходимости присваивать ему значение, если cheeseResponse
является no
.
поскольку cheeseChoice
используется только один раз для добавления выбора в список порядка, вы можете полностью отбросить переменную и встроить ее в добавление. Однако если явное объявление cheeseChoice
поможет вам прочитать и понять код, тогда его можно оставить там. В конце концов, есть хороший аргумент в пользу того, что удобочитаемость является наиболее важным свойством кода (помимо, конечно, правильной работы).
Если строка кода становится слишком длинной, вы также можете перенести ее на следующую строку, чтобы сделать ее более читаемой. Я использовал явный разрыв строки () Вот. Более подробную информацию можно найти в этом вопросе.
if cheeseResponse == 'yes':
customerOrder.append(pyip.inputMenu(['cheddar', 'swiss', 'mozzarella'],
'Please choose your cheese:n', lettered=True))
Соответственно в
choice=""
for i in extras:
choice = pyip.inputYesNo('Would you like ' + i +'?n')
if choice == 'yes':
customerOrder.append(i)
else:
choice=""
оператор else не нужен. Однако первоначальное объявление choice
(поскольку затем он переназначается внутри цикла for. В цикле for вы можете использовать более подробную переменную вместо i
который обычно используется при увеличении int-counter в чем-то вроде for i in range(10):
. Лично я предпочитаю for extra in extras:
Вот.
for extra in extras:
choice = pyip.inputYesNo('Would you like ' + extra +'?n')
if choice == 'yes':
customerOrder.append(extra)
или встроенный:
for extra in extras:
if pyip.inputYesNo('Would you like ' + extra +'?n') == 'yes':
customerOrder.append(extra)
Удачи в ваших усилиях.
Большое вам спасибо за то, что нашли время сломать это и помочь мне.
— tylerc515