首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >咖啡机实践项目

咖啡机实践项目
EN

Code Review用户
提问于 2023-05-31 22:25:25
回答 1查看 70关注 0票数 2

这只是一种简单的练习来触及我学到的一些东西。我还试图考虑到Pep 8格式实践,以我的能力和适用的意见,从我以前的帖子。很难将凹痕保持在2级以内。建设性的批评很受赞赏。

main.py

代码语言:javascript
复制
from time import sleep
import sys
from resources import MENU, resources

ACTIONS = ['espresso', 'latte', 'cappuccino', 'report', 'off', 'e', 'l', 'c', 'r', 'o']
FORMAT = '.2f'
TIME = 1.5


def perform_action(choice):
    """ Main loop for paying for and receiving a coffee type"""
    if choice in ['espresso', 'e']:
        choice = 'espresso'
        payment = paying(choice)
        prepare(choice, payment)
    elif choice in ['latte', 'l']:
        choice = 'latte'
        payment = paying(choice)
        prepare(choice, payment)
    elif choice in ['cappuccino', 'c']:
        choice = 'cappuccino'
        payment = paying(choice)
        prepare(choice, payment)
    elif choice in ['report', 'r']:
        for item in resources:
            print(f'{item.title()}: {resources[item] if item != "money" else f"${format(resources[item], FORMAT)}"}\n')
    else:
        sys.exit()


def paying(drink):
    """ Checks for payment and its viability and returns how much you paid"""
    cost = MENU[drink]['cost']
    quarter, dime, nickel, penny = 'None', 'None', 'None', 'None'

    while not quarter.isdigit():
        quarter = (input('How many quarters? '))
    while not dime.isdigit():
        dime = (input('How many dimes? '))
    while not nickel.isdigit():
        nickel = (input('How many nickels? '))
    while not penny.isdigit():
        penny = (input('How many pennies? '))

    quarter, dime, nickel, penny = int(quarter) * .25, int(dime) * .10, int(nickel) * .05, int(penny) * .01
    pay = (quarter + dime + nickel + penny)

    if pay < cost:
        print(f'Not enough payment!, refunding ${pay}...\n')
        sleep(TIME)
        return [False,pay]
    elif pay > cost:
        change = pay - cost
        pay = pay - change
        print(f'Returning change of ${format(change, FORMAT)}...\n')
        sleep(TIME)

    print(f'Payment Successful!, ordering {drink}...\n')
    sleep(TIME)
    resources['money'] += pay
    return [True, pay]


def prepare(drink, pay):
    """Calculates and removes resources from coffee machine if drink resources don't exceed it."""
    prepared = pay[0]

    if prepared:
        for i in resources:
            if i == 'money':
                continue
            if drink == 'espresso' and i == 'milk':
                continue
            if resources[i] < MENU[drink]['ingredients'][i]:
                prepared = False
                print(f'Not enough {i}.')
            if prepared:
                resources[i] -= MENU[drink]['ingredients'][i]

        if prepared:
            print(f'Enjoy your {drink}!\n')
            sleep(TIME)
        else:
            print(f'Refunding {pay[1]}....')
            resources['money'] -= pay[1]


running = True
while running:
    action = None
    while action not in ACTIONS:
        action = input(
            "\nCoffee:\n\t⚪Espresso[E]: $1.50\n\t⚪Latte[L]: $2.50\n\t⚪Cappuccino[C]: "
            "$3.00\n\nCommands:\n\t⚪Report[R]\n\t⚪Off[O]\n\n"
            "What would you like? "
        ).lower()
        print('\n')

    perform_action(action)

资源.

代码语言:javascript
复制
MENU = {
    "espresso": {
        "ingredients": {
            "water": 50,
            "coffee": 18,
        },
        "cost": 1.5,
    },
    "latte": {
        "ingredients": {
            "water": 200,
            "milk": 150,
            "coffee": 24,
        },
        "cost": 2.5,
    },
    "cappuccino": {
        "ingredients": {
            "water": 250,
            "milk": 100,
            "coffee": 24,
        },
        "cost": 3.0,
    }
}

resources = {
    "water": 300,
    "milk": 200,
    "coffee": 100,
    "money": 0
}
EN

回答 1

Code Review用户

发布于 2023-06-01 01:27:51

对于初学者来说,这是个不错的开始。

ACTIONS不是很好地表示为字符串列表。相反,可以考虑使用字符串到操作函数的字典。通过使用partial,可以将动作函数预绑定到参数(如饮料产品)。

perform_action中有重复的代码。您可以通过维护一个简单的产品及其价格数据库来减少这种重复。

类似地,paying中也有重复的代码(应该称为pay,相关的祈使时态动词)。考虑循环处理一系列硬币定义。

不要sleep。这是一个UI反功能,我通常认为它是在欺骗你的用户(看起来这个程序在做一些值得等待的事情,而不是)。

FORMAT并不像硬编码常量那样有用。在简单的情况下,我只需要在使用.2f的地方编写它;但是更容易移植的事情是使用来自locale模块的货币格式支持。

sys.exit()perform_action内部调用不是件好事(实际上,在这个程序中的任何地方)。您应该使用return来优雅地展开堆栈,而不是退出。

您的变量quarter等从字符串开始,最后作为浮动结束。不要在使用过程中改变变量的类型。

将PEP484类型提示添加到函数签名中。

将从running = True开始的主循环移到main()函数中,以便这些变量不是全局变量,这样单元测试人员就可以有选择地导入某些函数,同时避免主入口点。出于这个原因,还添加了一个if name == '__main__'守卫。

resources.py作为JSON文件比作为代码更好。

if drink == 'espresso' and i == 'milk'不应该是硬编码的。只要循环一下资源。

建议

一些中等水平的东西,但没有什么是你学不到的。

资源.

代码语言:javascript
复制
[
    {
        "name": "espresso",
        "cost": 1.50,
        "ingredients": {
            "water": 50,
            "coffee": 18
        }
    },
    {
        "name": "latte",
        "cost": 2.50,
        "ingredients": {
            "water": 200,
            "milk": 150,
            "coffee": 24
        }
    },
    {
        "name": "cappuccino",
        "cost": 3.00,
        "ingredients": {
            "water": 250,
            "milk": 100,
            "coffee": 24
        }
    }
]

main.py

代码语言:javascript
复制
import json
from functools import partial
from locale import currency, setlocale, LC_ALL
from typing import Callable, Iterator, Any, NamedTuple

resources = {
    "water": 300,
    "milk": 200,
    "coffee": 100,
    "money": 0
}


class Denomination(NamedTuple):
    plural: str
    cents: int

    def input_tender(self) -> int:
        count = int(input(f'How many {self.plural}? '))
        if count < 0:
            raise ValueError('Cannot tender a negative coin count')
        return count * self.cents


DENOMINATIONS = (
    Denomination('quarters', 25),
    Denomination('dimes', 10),
    Denomination('nickels', 5),
    Denomination('pennies', 1),
)


def pay(drink: dict[str, Any]) -> float:
    """Checks for payment and its viability and returns how much you paid"""
    cost: float = drink['cost']
    tender_cents = 0

    for coin in DENOMINATIONS:
        while True:
            try:
                tender_cents += coin.input_tender()
                break
            except ValueError:
                pass

    tender = tender_cents*0.01
    if tender < cost:
        print(f'Not enough payment! Refunding {currency(tender)}...')
        return 0

    if tender > cost:
        change = tender - cost
        print(f'Returning change of {currency(change)}...')

    print(f'Payment Successful! Ordering {drink["name"]}...')
    return cost


def prepare(drink: dict[str, Any]) -> bool:
    """Calculates and removes resources from coffee machine if drink resources don't exceed it."""
    new_resources = dict(resources)

    for resource_name, needed in drink['ingredients'].items():
        new = resources[resource_name] - needed
        if new < 0:
            print(f'Not enough {resource_name}.')
            return False
        new_resources[resource_name] = new

    resources.update(new_resources)
    return True


def purchase(drink: dict[str, Any]) -> bool:
    """End-to-end purchase of one drink"""
    payment = pay(drink)
    if payment > 0:
        if prepare(drink):
            print(f'Enjoy your {drink["name"]}!')
            resources['money'] += payment
        else:
            print(f'Refunding {currency(payment)}...')
    return True


def make_menu_description(
    drinks: list[dict[str, Any]], commands: dict[str, Callable],
) -> Iterator[str]:
    """Iterator of strings (one line each) representing the menu"""

    yield 'Coffee:'
    for drink in drinks:
        yield (
            f'\t⚪{drink["name"].title()}'
            f'[{drink["name"][0].upper()}]: '
            f'{currency(drink["cost"])}'
        )
    yield ''

    yield 'Commands:'
    for name in commands.keys():
        yield (
            f'\t⚪{name.title()}'
            f'[{name[0].upper()}]'
        )
    yield ''


def make_menu_choices(
    drinks: list[dict], commands: dict[str, Callable],
) -> Iterator[tuple[
    str,  # menu key
    Callable[[], bool]  # menu action callable
]]:
    """Make the menu choice key-value iterator; all values are no-argument callables that return
    True if the program should continue"""

    for drink in drinks:
        bound_purchase = partial(purchase, drink=drink)
        yield drink['name'][0].upper(), bound_purchase

    for name, command in commands.items():
        yield name[0].upper(), command


def report() -> bool:
    for item, amount in resources.items():
        if item == 'money':
            desc = currency(amount)
        else:
            desc = amount
        print(f'{item.title()}: {desc}')

    return True


def off() -> bool:
    # signal main loop to quit
    return False


def main() -> None:
    setlocale(LC_ALL, '')
    with open('resources.json') as f:
        drinks = json.load(f)
    commands = {'report': report, 'off': off}
    menu_desc = '\n'.join(make_menu_description(drinks, commands))
    choices = dict(make_menu_choices(drinks, commands))

    while True:
        print(menu_desc)
        choice_str = input('What would you like? ')
        perform_action = choices.get(choice_str[:1].upper())
        if perform_action is not None and not perform_action():
            break
        print()


if __name__ == '__main__':
    main()
票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/285277

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档