今天,我学习了OOP的基础知识。我试图将它们应用于此咖啡机项目。但是我还是一个初学者,所以我觉得我的代码可以改进。我是否可以遵循任何提示,技巧或其他其他建议来改善外观,运行时,可读性或充分利用OOP?

该代码模拟了一种要求4种不同操作的咖啡机。购买,填充,剩余和退出。

输入“购买”时,程序会询问您想要哪种咖啡。如果您改变主意获取咖啡,则可以在此处输入1、2、3或返回。每种咖啡对制作咖啡所需的用品都有不同的要求。如果咖啡机中的可用耗材不足,则不会煮咖啡,并且会出现提示。如果有足够的耗材,则会从可用的耗材中扣除所选咖啡的要求,并显示提示其成功的提示。

剩余的显示咖啡机中每种物料的当前耗材量。例如水,牛奶,咖啡豆,杯子和金钱。

退出允许用户停止该程序。

 CoffeeMachine 


评论

好吧,我确实在程序中添加了注释以使其更容易

感谢您的补充说明。希望您现在得到一个好的答案:D

仅供参考,您在这里构建的称为状态机。也就是说,机器具有特定的内部状态,它接受一系列操作作为输入,每个操作可能会或可能不会导致状态更改,并且可能会或可能不会导致基于操作和状态的输出。关于状态机的研究以及如何有效地实现它们的文献很多。您可能需要阅读一些内容。

请不要根据给出的答案来修正问题中的代码,这会使其他读者无法理解实际的评论。

#1 楼

类/实例变量

在代码中,您使用类变量而不是实例变量。

您必须知道类变量在所有实例中都是共享的,例如:

 class CoffeeMachine:
    water = 400


my_machine = CoffeeMachine()
your_machine = CoffeeMachine()

CoffeeMachine.water = 0
print(my_machine.water)
print(your_machine.water)
 


您在两台计算机上都得到0!

右边方法是使用实​​例变量。实例变量确定对象的状态:

 class CoffeeMachine:
    def __init__(self):
        self.water = 400


my_machine = CoffeeMachine()
your_machine = CoffeeMachine()

my_machine.water = 0
print(my_machine.water)
print(your_machine.water)
 


因此,在您的代码中,您可以可以用CoffeeMachine.sothing替换self.sothing

请参阅Python文档中的类和实例变量一章。

您的构造函数将变为:

 class CoffeeMachine:
    def __init__(self):
        self.water = 400
        self.milk = 540
        self.coffee_beans = 120
        self.cups = 9
        self.money = 550
        self.running = False
 


无限递归

我检测到了潜在的无限递归:


__init__函数调用`start``,
start函数调用动作,
每个动作调用return_to_menu
return_to_menu函数再次调用start

为避免这种情况,可以使用无限循环,该循环将由运行属性控制。
这是场景:

机器已初始化:运行是True

运行时为True


用户输入要执行的操作
机器执行操作

您可以轻松地将其转换为main函数:

 def main():
    machine = CoffeeMachine()
    while machine.running:
        action = ask_action()
        machine.execute_action(action)


if __name__ == '__main__':
    main()
 


当然,我们需要稍微更改一下实现:



初始化必须设置为True

 def __init__(self):
    ...
    self.running = False
 


旧的start方法分为两个功能,具有一个角色:提示用户并执行操作。
return_to_menu已删除。

提示用户

当您向用户提问时,通常需要检查输入内容以确保其符合我们的需求。如果不是,我们将永远循环。

对于ask_action函数,我们有一组可接受的答案:“购买”,“填充”,“收取”,“退出”,“剩余”。因此,我们可以一直循环直到用户输入可接受的答案为止。

在Python中,我们可以为此使用枚举: -override“> import enum class Action(enum.Enum): BUY = "buy" FILL = "fill" TAKE = "take" EXIT = "exit" REMAINING = "remaining"

这里是可能性的小演示:

 >>> possible_values = [action.value for action in Action]
>>> possible_values
['buy', 'fill', 'take', 'exit', 'remaining']

>>> action = Action("fill")
>>> action
<Action.FILL: 'fill'>

>>> action = Action("quit")
Traceback (most recent call last):
  ...
ValueError: 'quit' is not a valid Action
 


这里是定义ask_action函数的方法:

 import enum

class Action(enum.Enum):
    BUY = "buy"
    FILL = "fill"
    TAKE = "take"
    EXIT = "exit"
    REMAINING = "remaining"

def ask_action():
    possible_values = ", ".join([action.value for action in Action])
    while True:
        answer = input(f"Write action ({possible_values}):\n")
        try:
            return Action(answer)
        except ValueError:
            print(f"This answer is not valid: {answer}")
 


注意:ask_action是此处的函数,由于它不访问类变量或方法,因此无需将其转换为方法。

执行操作

很容易将旧的start方法更改为execute_action方法。该方法的参数为action: >

将退出操作更改为设置为def execute_action(self, action): if action == Action.BUY: self.buy() elif action == Action.FILL: self.fill() elif action == Action.TAKE: self.take() elif action == Action.EXIT: self.running = False elif action == Action.REMAINING: self.show_remaining() else: raise NotImplementedError(action) 运行。您忘记更新False

NotImplementedError(已重命名为Action)已修复:无需在参数中使用该类。

如您所见,这非常简单。

显示剩余的

execute_action函数已重命名为status以使用动词并匹配show_remaining中使用的术语。
但是您也可以将Action更改为“状态”。

状态不需要任何参数,因为您只想显示实例变量值。
因此,您可以编写:

 status 


您可以使用文档字符串来代替注释。通过这种方式,我们可以在Python中记录函数和类。

您可以阅读《 Hitchhiker Python指南》中的文档字符串和API文档。很好的书。

喝一杯

“购买”动作与“ ask_action / execute_action”类似。
如果使用相同的逻辑,您会看到也可以删除或重新实现show_remaining函数。

不同之处在于,您希望用户输入数字而不是文本。
您具有:1-“ espresso”,2-“ latte”,3-“ cappuccino”,对于“返回主菜单”,您可以选择9。
所有可以存储在Python Action类中的对象都可以在数字和标签之间进行映射。

请注意,def show_remaining(self): """ Display the quantities of supplies in the machine at the moment """ print(f"The coffee machine has:") print(f"{self.water} of water") print(f"{self.milk} of milk") print(f"{self.coffee_beans} of coffee beans") print(f"{self.cups} of disposable cups") print(f"${self.money} of money") 是此功能的好称呼:

备注:




deduct_supplies是必需的,因为dict键是无序的(实际上,最新版本的Python会保持键顺序),
使用ask_drink是检查密钥是否在字典中的好方法。

消费(推导的用品)

在咖啡机中,推导的用品是以5个元素的列表表示。
例如,对于水,牛奶,咖啡豆,杯子和金钱,我们有def ask_drink(): choices = {1: "espresso", 2: "latte", 3: "cappuccino", 9: "back to main menu"} possible_values = ", ".join(f"{value} - {name}" for value, name in sorted(choices.items())) while True: answer = input(f"What do you want to buy? ({possible_values}):\n") try: value = int(answer) if value in choices: return value print(f"This answer is not valid: {answer}") except ValueError: print(f"This is not a number: {answer}")
如果有列表,则需要按索引访问这些项目。但是我会更容易按名称访问这些项目。为此,您可以使用sorteddict是工厂函数,它创建一个类(value in choices的子类)。首先,您可以定义一个新的元组类,我们称之为[250, 0, 16, 1, 4]

 collections.namedtuple 


您可以像经典namedtuple或通过键/值对来实例化tuple

 Consumption 


注意:第二种形式的可读性更高。

检查可用性

当您需要“检查”某些东西时,您可以考虑例外情况。其背后的想法是:我进行一些测试,如果出现问题,则引发异常。异常类型和/或异常消息可以详细说明问题。然后,我可以使用异常处理程序来显示消息。

定义异常,一个好的做法是继承import collections Consumption = collections.namedtuple("Consumption", "water, milk, coffee_beans, cups, money") 类,如下所示:

 Consumption 


此异常采用供应参数,它是缺少的供应的名称。

然后可以实现tuple方法如下:

 espresso_cons = Consumption(250, 0, 16, 1, 4)
latte_cons = Consumption(water=350, milk=75, coffee_beans=20, cups=1, money=7)
cappuccino_cons = Consumption(water=200, milk=100, coffee_beans=12, cups=1, money=6)
 


真的很简单,不是吗?

Exception方法

您知道要实现class NotEnoughSupplyError(Exception): def __init__(self, supply): msg = f"Sorry, not enough {supply}" super(NotEnoughSupplyError, self).__init__(msg) 方法的所有要素:

 available_check 


要获得def available_check(self, consumption): """ Checks if it can afford making that type of coffee at the moment :param consumption: the Consumption :raise NotEnoughSupplyError: if at least one supply is missing. """ if self.water - consumption.water < 0: raise NotEnoughSupplyError("water") elif self.milk - consumption.milk < 0: raise NotEnoughSupplyError("milk") elif self.coffee_beans - consumption.coffee_beans < 0: raise NotEnoughSupplyError("coffee beans") elif self.cups - consumption.cups < 0: raise NotEnoughSupplyError("cups") ,我们在每个饮料值和每个buy实例之间引入一个小的映射。

当然,可以使用异常处理程序来代替经典buy。但我想向您展示强大的功能。

def buy(self): drink = ask_drink() if drink == 9: return espresso_cons = Consumption(250, 0, 16, 1, 4) latte_cons = Consumption(water=350, milk=75, coffee_beans=20, cups=1, money=7) cappuccino_cons = Consumption(water=200, milk=100, coffee_beans=12, cups=1, money=6) consumption = {1: espresso_cons, 2: latte_cons, 3: cappuccino_cons}[drink] try: self.available_check(consumption) except NotEnoughSupplyError as exc: print(exc) else: print("I have enough resources, making you a coffee!") self.water -= consumption.water self.milk -= consumption.milk self.coffee_beans -= consumption.coffee_beans self.cups -= consumption.cups self.money += consumption.money 方法又一次,要实现consumption方法,您可以引入函数Consumption,该函数要求给定数量的给定值。供应。该函数在参数中输入一条消息:

 if 


fill方法可以实现如下:

 fill 


ask_quantity方法。

不确定了解def ask_quantity(msg): while True: answer = input(msg + "\n") try: value = int(answer) if value >= 0: return value print(f"This answer is not valid: {answer}") except ValueError: print(f"This is not a number: {answer}") 方法的作用:金钱总是重置为0 !?

将所有内容放在一起

如您所见,我已经做了很多改进。您当然可以走得更远,但写一些简单易读的内容。

 fill 


海事组织,钱不应该像水一样供应...

评论


\ $ \ begingroup \ $
您可能希望在第一部分中添加,如果不是Coffee.Machine.running,则应成为非self.running的。
\ $ \ endgroup \ $
–mkrieger1
20年4月14日在11:38

\ $ \ begingroup \ $
关于您的“无限递归”部分,我想指出,一个状态尾部调用下一个状态是实现状态机的一种广泛使用的模式。而且,正如这里的问题以及Stack Overflow上的许多其他问题所显示的那样,对于像OP这样的初学者来说,这似乎也是一种非常自然的建模方法。而且,正如Guy L. Steele和其他人所解释的,尾部调用对于OOP是至关重要的。因此,OP如何建模状态机并没有天生的错误,实际上,这是建模状态机的标准OOP方法。只是Python的局限性阻止了…
\ $ \ endgroup \ $
–Jörg W Mittag
20年4月15日在15:48

\ $ \ begingroup \ $
…解决方案。 (有人会认为这不是Python的局限性,而是Guido van Rossum的局限性,因为有许多Pythonistas和Python实现者希望在Python中进行适当的尾部调用,但是Guido van Rossum明确禁止任何Python实现提供它们。)
\ $ \ endgroup \ $
–Jörg W Mittag
20年4月15日在15:50

#2 楼

当您有多个if语句时(如您的代码一样),这表明您可以在代码中使用访问者模式。我将在示例中使用字典。

您的代码:

def start(self):
    self.running = True
    self.action = input("Write action (buy, fill, take, remaining, exit):\n")
    print()
    #possible choices to perform in the coffee machine
    if self.action == "buy":
        self.buy()
    elif self.action == "fill":
        self.fill()
    elif self.action == "take":
        self.take()
    elif self.action == "exit":
        exit()
    elif self.action == "remaining":
        self.status()


用访问者模式重写:

def action_buy(self):
    self.buy()

action_choices = { "buy" : action_buy,
                   "fill" : action_fill, ...

def start(self):
    self.running = True
    self.action = input("Write action (buy, fill, take, remaining, exit):\n")
    print()
    #possible choices to perform in the coffee machine
    if self.action in action_choices:
        action_choices[self.action](self)


您可以在购买功能上使用相同的原理。我没有验证代码,所以可能有一些错误,但希望您明白。

评论


\ $ \ begingroup \ $
真的很有用,谢谢
\ $ \ endgroup \ $
–DeltaHaxor
20年4月12日在20:45

\ $ \ begingroup \ $
更好的是,您可以使用字典的键来生成提示文本:input(“ Write action({actions}):\ n” .format(actions = action_choices.keys())
\ $ \ endgroup \ $
– Kroltan
20-4-13在17:11

#3 楼

您应该拆分业务逻辑和用户界面。

获得先进的咖啡机并可以与人交谈是很常见的事。
我始终将逻辑的核心构建为尽可能通用的。这将使项目的测试,重用和隔离变得更加容易。

这意味着将CoffeeMachine更改为仅包含availablededuct作为方法。一个可以是CoffeeInterface的类cmd.Cmd
这将有助于删去您现在拥有的一些代码。在self.foo中。其他所有内容都应通过参数传递。

我也不会更改__init__中定义的任何属性,因为仅应在其中定义与该类直接相关的内容。

请勿执行__init__中的if not CoffeeMachine.running: self.start()之类的操作。您应该让用户致电__init__

.start()不能在实际的实时程序中使用。相反,您应该对代码进行结构化,以便不需要它。

有时exit()exit(1)很有用。但是除非您正在进行Unix编程,否则不太可能不需要这些。


所有这些都可以得到以下代码。变化不大,因为我主要只是将这两个类分开。

 raise SystemExit(1) 


现在我们将两部分分开了,我们可以集中精力检查代码。命名元组具有以下优点:


它是不可变的,这意味着很难弄乱class CoffeeMachine: def __init__(self, water, milk, coffee_beans, cups, money): self.water = water self.milk = milk self.coffee_beans = coffee_beans self.cups = cups self.money = money def available(self, water, milk, coffee_beans, cups, _): not_available = "" if self.water - water < 0: not_available = "water" elif self.milk - milk < 0: not_available = "milk" elif self.coffee_beans - coffee_beans < 0: not_available = "coffee beans" elif self.cups - cups < 0: not_available = "disposable cups" if not_available != "": print(f"Sorry, not enough {not_available}!") return False else: print("I have enough resources, making you a coffee!") return True def deduct(self, water, milk, coffee_beans, cups, money): self.water -= water self.milk -= milk self.coffee_beans -= coffee_beans self.cups -= cups self.money += money class CoffeeInterface(cmd.Cmd): def __init__(self, coffee_machine, *args, **kwargs): super().__init__(*args, **kwargs) self.coffee_machine = coffee_machine def do_buy(self, _): choice = input("What do you want to buy? 1 - espresso, 2 - latte, 3 - cappuccino, back - to main menu:\n") if choice == '1': requirements = [250, 0, 16, 1, 4] if self.coffee_machine.available(*requirements): self.coffee_machine.deduct(*requirements) elif choice == '2': requirements = [350, 75, 20, 1, 7] if self.coffee_machine.available(*requirements): self.coffee_machine.deduct(*requirements) elif choice == "3": requirements = [200, 100, 12, 1, 6] if self.coffee_machine.available(*requirements): self.coffee_machine.deduct(*requirements) elif choice == "back": # if the user changed his mind pass def do_fill(self, _): """Add supplies to the machine.""" self.coffee_machine.water += int(input("Write how many ml of water do you want to add:\n")) self.coffee_machine.milk += int(input("Write how many ml of milk do you want to add:\n")) self.coffee_machine.coffee_beans += int(input("Write how many grams of coffee beans do you want to add:\n")) self.coffee_machine.cups += int(input("Write how many disposable cups of coffee do you want to add:\n")) def do_take(self, _): """Take money from the machine.""" print(f"I gave you ${self.coffee_machine.money}") self.coffee_machine.money -= self.coffee_machine.money def do_status(self): """Display the quantities of supplies in the machine at the moment.""" print(f"The coffee machine has:") print(f"{self.coffee_machine.water} of water") print(f"{self.coffee_machine.milk} of milk") print(f"{self.coffee_machine.coffee_beans} of coffee beans") print(f"{self.coffee_machine.cups} of disposable cups") print(f"${self.coffee_machine.money} of money") CoffeeInterface(CoffeeMachine(400, 540, 120, 9, 550)).cmdloop()
从中获取特定值是干净的CoffeeMachine.available而不是reduced.water
我们可以绕过一个对象,而不用使用讨厌的reduced[0]

我选择使用*requirements,但是由于typing.NamedTuple不使用类型提示,因此可能更易于理解。


我将在collections.namedtuple类上定义__sub__ dunder方法。
这意味着当我们减少耗材(代码的核心)时,对您而言就更好了。

要使此功能正常运行,您可以选择使Supplies与其余功能不同。或者,当您支付一杯饮料的费用时,您可能会赚钱。我认为制作饮料的成本是最直观的。

money的代码可以减少到更少的行,但是此刻这会使代码更加混乱。会将availableprint函数移出,最好放在available中。
要允许打印丢失的项目,可以将其名称更改为do_buy并返回不可用的项目。
仍然有意义。

您应该将可用饮料从unavailable中移出。
如果将它们放入字典中,则可以大大减少do_buy中的代码量。

为此,我们可以构建一个字典,每个键的值分别为1、2或3。该值作为该饮料的do_buy
从这里我们可以使用Supplies,它将返回dict.get(choice, None)如果用户未输入有效的选择,则为所选饮料或Supplies

如果不是有效选择,我们可以从此处返回要与None交互,否则。

为了简化CoffeeMachinedo_fill,我们可以添加take dunder方法。这意味着我们只需要一个__add__而不是四个。

 + 



鉴于import cmd from typing import NamedTuple class Supplies(NamedTuple): water: int milk: int coffee_beans: int cups: int money: int def __sub__(self, other): return Supplies( self.water - other.water, self.milk - other.milk, self.coffee_beans - other.coffee_beans, self.cups - other.cups, self.money - other.money, ) def __add__(self, other): return Supplies( self.water + other.water, self.milk + other.milk, self.coffee_beans + other.coffee_beans, self.cups + other.cups, self.money + other.money, ) DRINKS = { '1': Supplies(250, 0, 16, 1, -4), '2': Supplies(350, 75, 20, 1, -7), '3': Supplies(200, 100, 12, 1, -6), } class CoffeeMachine: def __init__(self, supplies): self.supplies = supplies def unavailable(self, drink): remaining = self.supplies - drink not_available = "" if remaining.water < 0: not_available = "water" elif remaining.milk < 0: not_available = "milk" elif remaining.coffee_beans < 0: not_available = "coffee beans" elif remaining.cups < 0: not_available = "disposable cups" return not_available if not_available else None def deduct(self, drink): self.supplies -= drink class CoffeeInterface(cmd.Cmd): def __init__(self, coffee_machine, *args, **kwargs): super().__init__(*args, **kwargs) self.coffee_machine = coffee_machine def do_buy(self, _): choice = input("What do you want to buy? 1 - espresso, 2 - latte, 3 - cappuccino, back - to main menu:\n") drink = DRINKS.get(choice, None) if drink is None: return unavailable = self.coffee_machine.available(drink) if unavailable: print(f"Sorry, not enough {unavailable}!") else: print("I have enough resources, making you a coffee!") self.coffee_machine.deduct(drink) def do_fill(self, _): """Add supplies to the machine.""" self.coffee_machine.supplies += Supplies( int(input("Write how many ml of water do you want to add:\n")), int(input("Write how many ml of milk do you want to add:\n")), int(input("Write how many grams of coffee beans do you want to add:\n")), int(input("Write how many disposable cups of coffee do you want to add:\n")), 0, ) def do_take(self, _): """Take money from the machine.""" money = self.coffee_machine.supplies.money print(f"I gave you ${money}") self.coffee_machine.supplies -= Supplies(0, 0, 0, 0, money) def do_status(self): """Display the quantities of supplies in the machine at the moment.""" supplies = self.coffee_machine.supplies print(f"The coffee machine has:") print(f"{supplies.water} of water") print(f"{supplies.milk} of milk") print(f"{supplies.coffee_beans} of coffee beans") print(f"{supplies.cups} of disposable cups") print(f"${supplies.money} of money") CoffeeInterface(CoffeeMachine(Supplies(400, 540, 120, 9, 550))).cmdloop() 的数量,应该可以明显看出self.coffee_machine.supplies.{x}现在更多



必须阅读和编写CoffeeMachine很烦人。
我们可以轻松地将self.coffee_machine.supplies更改为deduct
功能self.coffee_machine.supplies -= drink可以移至unavailableSupplies上。它定义了一种无需重写就可以迭代的方法。
这意味着我们可以简化CoffeeInterfaceNamedTuple__sub__方法。

为此,我们可以使用__add__我们可以同时迭代两件事。

 unavailable 


我们还可以使用列表理解或生成器表达式以在一行上构建新的zip


使用foos = 'abcdef' bars = 'ghijkl' # non-zip for i in range(len(foos)): print(foos[i], bars[i]) # zip for foo, bar in zip(foos, bars): print(foo, bar) 可以在输入命令时传递字符串。
这意味着可以输入Supplies

如果您用它来购买商品名称而不是奇数,那会很酷。

要允许这样做,您可以使用菜单选项来显示您可以购买的物品。


cmd

#4 楼

首先是一些基本的观察:


您的running变量在所有CoffeeMachine对象之间共享-一旦创建了一个CoffeeMachine,它便会自动启动,然后再在其中的任何位置创建CoffeeMachine Universe也正在“运行”,因此它不会自行启动!除非这是您希望拥有的属性(它肯定与现实世界的咖啡机行为不匹配),否则应将running设置为实例变量(即,将其放在__init__中),或者更好的是,不要使用它全部(因为无论如何您都立即对其进行初始化,此后再也不要将其用于其他任何用途-如果一台计算机在创建后始终始终在“运行”,则没有必要用bool来指示该状态)。
您的实例变量是在__init__之后创建的。 Python允许您执行此操作,但是这被认为是不好的做法,因为在初始化变量之前很容易出现错误,您可以在其中访问变量。通常,所有实例变量都应在__init__中声明。
您的self.reduced变量仅用于available_checkdeduct_supplies,它们在设置reduced后立即被调用-reduced应该只是一个参数。如果它是一个参数,那么您知道在这些函数返回之后它的值无关紧要(这种情况),并且您不必担心self.reduced的代码设置的其他部分可能会受到影响。此处的一般规则是,状态应尽可能“短暂”和/或“范围狭窄”。 (编辑:当我遍历代码的其余部分时,我发现有一种常见的将值分配给self的模式,在该模式下,本地范围的值就足够了。永远不要使数据具有比需要的持久性!)

现在,对方法的结构进行更多的“概述”说明:


您的所有操作都将回调到return_to_menu,后者又回调到start。也许start应该循环播放?这样一来,不需要在每个操作方法的末尾都调用return_to_menu,对于阅读您的start方法的人来说,这实际上是一个循环也是显而易见的(您希望代码的工作方式对阅读它的每个人都显而易见) 。
将不同类型的对象指定为Enum s使得跟踪可能的值以及使代码的不同部分具有不同版本变得容易一些。
当不同对象之间存在关联时数据(例如“配料类型”和“数量”),这是在字典中存储的自然方式。同样,这使跟踪事情变得更加容易,并且无需复制和粘贴即可轻松地说出“对每种成分都做到这一点”。

我在这段代码上做了几步看看我是否可以将所有内容都转换为枚举和字典,其总体目标是不必在多个位置复制或粘贴相同的单词,并将所有这些if ... elif链转换为迭代或查找。我遵循的一般模式是让枚举的“名称”成为您在代码中引用它的方式,而“值”成为用户可见的呈现方式(通常但不总是相同);在现实生活中,您可能会有一个稍微复杂(且可扩展)的映射,以便进行本地化等,但是作为该概念的一般说明,我认为这已经足够了。

这就是我的想法;前面已经声明了很多数据来定义咖啡机的运行方式,而方法中的实际代码则少得多。

from enum import Enum, auto
from typing import Dict, List

class Inventory(Enum):
    """Inventory items."""
    water = "water"
    milk = "milk"
    coffee_beans = "coffee beans"
    cups = "disposable cups"
    money = "money"

# The unit description of each inventory item.
UNITS = { 
    Inventory.water: "ml of",
    Inventory.milk: "ml of",
    Inventory.coffee_beans: "grams of",
    Inventory.cups: "of",
    Inventory.money: "of",
}

class Action(Enum):
    """Menu actions."""
    buy = "buy"
    fill = "fill"
    take = "take"
    status = "remaining"

class Product(Enum):
    """Products for sale."""
    espresso = "1"
    latte = "2"
    cappuccino = "3"

# The cost of each product.
COSTS = { 
    Product.espresso: {
        Inventory.water: 250,
        Inventory.milk: 0,
        Inventory.coffee_beans: 16,
        Inventory.cups: 1,
        Inventory.money: 4,
    },
    Product.latte: {
        Inventory.water: 350,
        Inventory.milk: 75,
        Inventory.coffee_beans: 20,
        Inventory.cups: 1,
        Inventory.money: 7,
    },
    Product.cappuccino: {
        Inventory.water: 200,
        Inventory.milk: 100,
        Inventory.coffee_beans: 12,
        Inventory.cups: 1,
        Inventory.money: 6,
    },
}

class CoffeeMachine:

    def __init__(
        self, 
        water: int, 
        milk: int, 
        coffee_beans: int, 
        cups: int, 
        money: int
    ):
        self.quantities = {
            Inventory.water: water,
            Inventory.milk: milk,
            Inventory.coffee_beans: coffee_beans,
            Inventory.cups: cups,
            Inventory.money: money,
        }

        self.run()

    def run(self) -> None:
        do_action = {
            Action.buy: self.buy,
            Action.fill: self.fill,
            Action.take: self.take,
            Action.status: self.status,
        }
        actions = ', '.join(action.value for action in Action)

        while True:
            action = input(f"Write action ({actions}, exit):\n")
            print()
            if action == "exit":
                break
            do_action[Action(action)]()
            print()

    def available_check(self, cost: Dict[Inventory, int]) -> bool:
        """checks if it can afford making that type of coffee at the moment"""
        for item in Inventory:
            if self.quantities[item] < cost[item]:
                print(f"Sorry, not enough {item.value}!")
                return False
        else:
            print("I have enough resources, making you a coffee!")
            return True

    def deduct_supplies(self, cost: Dict[Inventory, int]) -> None:
        """performs operation from the cost list, based on the coffee chosen"""
        for item in Inventory:
            self.quantities[item] -= cost[item]

    def buy(self) -> None:
        products = ", ".join(
            f"{product.value} - {product.name}" for product in Product
        )
        choice = input(
            f"What do you want to buy? {products}, back - to main menu:\n"
        )
        if choice == "back":
            return
        cost = COSTS[Product(choice)]
        if self.available_check(cost):
            self.deduct_supplies(cost)

    def fill(self) -> None: 
        """for adding supplies to the machine"""
        for item in Inventory:
            if item == Inventory.money:
                continue
            self.quantities[item] += int(input(
                "Write how many "
                f"{UNITS[item]} {item.value}"
                " do you want to add:\n"
            ))

    def take(self) -> None:
        """for taking the money from the machine"""
        print(f"I gave you ${self.quantities[Inventory.money]}")
        self.quantities[Inventory.money] = 0

    def status(self) -> None: 
        """display the quantities of supplies in the machine at the moment"""
        print(f"The coffee machine has:")
        for item in Inventory:
            print(f"{self.quantities[item]} {UNITS[item]} {item.value}")

# specify the quantities of supplies at the beginning
# water, milk, coffee beans, disposable cups, money
CoffeeMachine(400, 540, 120, 9, 550) 


评论


\ $ \ begingroup \ $
哇,我怀疑用户会需要很多东西。您可能想将其作为一个单独的问题发布,因为仅需2秒的时间即可。这里也有很多PEP 8,PEP 257和Enum违规行为。
\ $ \ endgroup \ $
– Peilonrayz
20 Apr 13'0:23

#5 楼

代替

     def start(self):
        self.running = True
        self.action = input("Write action (buy, fill, take, remaining, exit):\n")
        print()
        #possible choices to perform in the coffee machine
        if self.action == "buy":
            self.buy()
        elif self.action == "fill":
            self.fill()
        elif self.action == "take":
            self.take()
        elif self.action == "exit":
            exit()
        elif self.action == "remaining":
            self.status()
 


我建议

  def start(self):
    self.running = True

    action = input("Write action (buy, fill, take, remaining, exit):\n")
    print()

    try:
      getattr(self, self.action)()
    except AttributeError:
      print("Invalid action")   


,但是您必须添加方法exit(self)remaining(self)

#6 楼

通过使用更多的对象,您可能会获得更多的OOP点。

首先定义一个异常: 。这简化了流控制。

接下来,定义一个将实际煮咖啡的工人,例如:

class NotAvailable(Exception):
   pass


这主要是您的代码,但是我更改了available_check()来引发上面定义的异常,并删除了return_to_menu()方法,因为工作人员可以在完成后简单地完成工作。 >
class Worker(object):
    def __init__(self):
      pass

    def list_actions(self):
      return ['buy', 'fill', 'take', 'remaining']

    def set_state(self,water,milk,coffee_beans,cups,money ):
        # quantities of items the coffee machine already had
        self.water = water
        self.milk = milk
        self.coffee_beans = coffee_beans
        self.cups = cups
        self.money = money

    def available_check(self): # checks if it can afford making that type of coffee at the moment
        self.not_available = "" # by checking whether the supplies goes below 0 after it is deducted
        if self.water - self.reduced[0] < 0:
            self.not_available = "water"
        elif self.milk - self.reduced[1] < 0:
            self.not_available = "milk"
        elif self.coffee_beans - self.reduced[2] < 0:
            self.not_available = "coffee beans"
        elif self.cups - self.reduced[3] < 0:
            self.not_available = "disposable cups"

        if self.not_available != "": # if something was detected to be below zero after deduction
            print(f"Sorry, not enough {self.not_available}!")
            raise NotAvailable

        else: # if everything is enough to make the coffee
            print("I have enough resources, making you a coffee!")
            return True

    def deduct_supplies(self):
# performs operation from the reduced list, based on the coffee chosen
        self.water -= self.reduced[0]
        self.milk -= self.reduced[1]
        self.coffee_beans -= self.reduced[2]
        self.cups -= self.reduced[3]
        self.money += self.reduced[4]

    def buy(self):
        self.choice = input("What do you want to buy? 1 - espresso, 2 - latte, 3 - cappuccino, back - to main menu:\n")
        if self.choice == '1':
            self.reduced = [250, 0, 16, 1, 4] # water, milk, coffee beans, cups, money
            self.available_check() # checks if supplies are available
            self.deduct_supplies() # if it is, then it deducts

        elif self.choice == '2':
            self.reduced = [350, 75, 20, 1, 7]
            self.available_check()
            self.deduct_supplies()

        elif self.choice == "3":
            self.reduced = [200, 100, 12, 1, 6]
            self.available_check() 
            self.deduct_supplies()       

        elif self.choice != 'back':
            print ("Choice not recognised")


    def fill(self): # for adding supplies to the machine
        self.water += int(input("Write how many ml of water do you want to add:\n"))
        self.milk += int(input("Write how many ml of milk do you want to add:\n"))
        self.coffee_beans += int(input("Write how many grams of coffee beans do you want to add:\n"))
        self.cups += int(input("Write how many disposable cups of coffee do you want to add:\n"))

    def take(self): # for taking the money from the machine
        print(f"I gave you ${self.money}")
        self.money -= self.money

    def remaining(self): # to display the quantities of supplies in the machine at the moment
        print(f"The coffee machine has:")
        print(f"{self.water} of water")
        print(f"{self.milk} of milk")
        print(f"{self.coffee_beans} of coffee beans")
        print(f"{self.cups} of disposable cups")
        print(f"${self.money} of money")


将工作程序定义为单独的对象,可以使编程挑战中的不同任务之间更加清晰地分开。