该代码模拟了一种要求4种不同操作的咖啡机。购买,填充,剩余和退出。
输入“购买”时,程序会询问您想要哪种咖啡。如果您改变主意获取咖啡,则可以在此处输入1、2、3或返回。每种咖啡对制作咖啡所需的用品都有不同的要求。如果咖啡机中的可用耗材不足,则不会煮咖啡,并且会出现提示。如果有足够的耗材,则会从可用的耗材中扣除所选咖啡的要求,并显示提示其成功的提示。
剩余的显示咖啡机中每种物料的当前耗材量。例如水,牛奶,咖啡豆,杯子和金钱。
退出允许用户停止该程序。
CoffeeMachine
#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}")
。如果有列表,则需要按索引访问这些项目。但是我会更容易按名称访问这些项目。为此,您可以使用
sorted
。 dict
是工厂函数,它创建一个类(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
更改为仅包含available
和deduct
作为方法。一个可以是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
的代码可以减少到更少的行,但是此刻这会使代码更加混乱。会将available
从print
函数移出,最好放在available
中。要允许打印丢失的项目,可以将其名称更改为
do_buy
并返回不可用的项目。仍然有意义。
您应该将可用饮料从
unavailable
中移出。如果将它们放入字典中,则可以大大减少
do_buy
中的代码量。为此,我们可以构建一个字典,每个键的值分别为1、2或3。该值作为该饮料的
do_buy
。从这里我们可以使用
Supplies
,它将返回dict.get(choice, None)
如果用户未输入有效的选择,则为所选饮料或Supplies
。如果不是有效选择,我们可以从此处返回要与
None
交互,否则。为了简化
CoffeeMachine
和do_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
可以移至unavailable
或Supplies
上。它定义了一种无需重写就可以迭代的方法。这意味着我们可以简化
CoffeeInterface
,NamedTuple
和__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_check
和deduct_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")
将工作程序定义为单独的对象,可以使编程挑战中的不同任务之间更加清晰地分开。
评论
好吧,我确实在程序中添加了注释以使其更容易感谢您的补充说明。希望您现在得到一个好的答案:D
仅供参考,您在这里构建的称为状态机。也就是说,机器具有特定的内部状态,它接受一系列操作作为输入,每个操作可能会或可能不会导致状态更改,并且可能会或可能不会导致基于操作和状态的输出。关于状态机的研究以及如何有效地实现它们的文献很多。您可能需要阅读一些内容。
请不要根据给出的答案来修正问题中的代码,这会使其他读者无法理解实际的评论。