我是Python的新手,我为一个学校项目创建了一个“钓鱼模拟器”。基本上,它是随机使用的。我知道我的代码在结尾处是重复的,但我不知道如何简化它。

import time
import random
fishing = True
a = b = c = d = e = 0 #define multiple variables as same thing
print ("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print ("Welcome to Lake Tocowaga")
print ("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
time.sleep(1)
name = input("What is your name fisherman?")
answer = input("Would you like to go fishing, " + name + "?")
if answer.lower() == "no":
    fishing == False
while fishing == True:    
    time.sleep(1)
    answer = input("Throw out your line, or go home?")
    if answer == "go home":
        fishing = False
        er = float(e / (a + b + c + d))
        print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
        print("Thanks for playing " + name + "!")
        print("You caught:", str(a), "cod, ", str(b), "salmon, ", str(c), "shark, ", str(d), "wildfish. \nEfficiency Rate: ", str(er), ".")
    else:
        t = random.randrange(1, 7)
        if t == 1:
            a += 1
            print("You caught a cod!")
        elif t == 2:
            b += 1
            print("You caught a salmon!")
        elif t == 3:
            c += 1
            print("You caught a shark!")
        elif t == 4:
            d += 1
            print("You caught a wildfish!")
        elif t >= 5:
            e += 1
            print("You caught nothing!")


评论

为什么将这个问题标记为python-2.x?我不认为它可以在Python 2中正常工作。

#1 楼

欢迎使用CodeReview。养成良好的编码习惯永远不会太早,而审查代码是实现此目标的最佳方法。

首先,祝贺您编写了一个干净,直接的程序。虽然您确实有一些问题(如下),但它们并不是主要问题,并且您的程序似乎适合其级别。

现在,解决问题;-)

使用空白

Python要求您使用水平空白。但是,您还应该使用垂直空格(也称为“空白行”)将代码的不同部分组织到段落中。

这个巨大的块:

import time
import random
fishing = True
a = b = c = d = e = 0 #define multiple variables as same thing
print ("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print ("Welcome to Lake Tocowaga")
print ("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
time.sleep(1)
name = input("What is your name fisherman?")
answer = input("Would you like to go fishing, " + name + "?")
if answer.lower() == "no":
    fishing == False
while fishing == True:  


如果将其分解成这样的话,读起来会更好:

import time
import random

fishing = True
a = b = c = d = e = 0 #define multiple variables as same thing

print ("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print ("Welcome to Lake Tocowaga")
print ("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
time.sleep(1)

name = input("What is your name fisherman?")
answer = input("Would you like to go fishing, " + name + "?")

if answer.lower() == "no":
    fishing == False

while fishing == True:    


我所做的只是添加一些空行,但是我试图证明“这些东西放在一起”和“这些东西是顺序的但不相关”。

使用有意义的名称:

其中之一是鲨鱼?

a = b = c = d = e = 0


我不知道。但是,如果您对它们适当地命名:

cod = shark = wildfish = salmon = nothing = 0


我肯定会知道!

使用命名常量

此该行出现3次:

print ("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")


除非您正在复制/粘贴,否则很难获得正确数量的波浪号字符。如果这样做,可能会很痛苦。而是为波浪号创建一个名称。按照惯例,常量用大写字母拼写。 (它并不是一个常量,但是由于常量是用大写字母拼写的,因此如果您以大写字母命名,则不会修改它。)

H_LINE = "~" * 32

print(H_LINE)
print("Welcome to Lake Tocowaga")
print(H_LINE)
把最后一件事放到最后

这里有放所有东西的地方。一切都应该就位。打印摘要的位置将在底部。

您对while fishing:循环有个好主意。但是,当您响应用户输入时,与其立即打印摘要,不如更改变量并使循环失败,然后在底部打印摘要。它更“自然”(它使您的循环更易于阅读!)。

while fishing == True:    
    time.sleep(1)
    answer = input("Throw out your line, or go home?")
    if answer == "go home":
        fishing = False
        er = float(e / (a + b + c + d))
        print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
        print("Thanks for playing " + name + "!")
        print("You caught:", str(a), "cod, ", str(b), "salmon, ", str(c), "shark, ", str(d), "wildfish. \nEfficiency Rate: ", str(er), ".")
    else:
        ...


成为:

while fishing == True:    
    time.sleep(1)
    answer = input("Throw out your line, or go home?")
    if answer == "go home":
        fishing = False
    else:
        ...

er = float(e / (a + b + c + d))
print(H_LINE)
print("Thanks for playing " + name + "!")
print("You caught:", str(a), "cod, ", str(b), "salmon, ", str(c), "shark, ", str(d), "wildfish. \nEfficiency Rate: ", str(er), ".")


让内置函数发挥作用

您正在调用不需要调用的函数。整数之间的“真”除法结果为浮点数。您不需要致电float(e / (a + b + c + d))。而且,如果确实需要调用它,那将为时已晚!

同样,print知道如何处理整数和浮点数。当您可以执行以下操作时就不需要print(..., str(a), ...)

#2 楼

一些简单的事情。


a = b = c = d = e = 0


这是不好的,原因有两个:


这些都是无法描述的,过于简单的名称。仅仅通过查看就无法分辨它们代表什么。
您将它们的声明/定义全都推到了一行。通常认为这是不良做法。说我在寻找c的定义位置。当我可以确定要在某个地方精确查找它时,找到它要容易得多。在行中途声明时,很难找到它。

在两种情况下,为了简洁起见,您都在牺牲可读性。除非您要打高尔夫球,否则请避免这样做。可读性优先于几乎所有其他内容。


c = ...是文件中的第三行,但是直到以后才使用。除非是常量,否则最好在变量首次使用的位置附近声明变量。当某人正在阅读您的代码并想要查看fishing = True的定义时,如果他们只需要查找一两行而不是滚动到文件顶部,则效率更高。


fishing可以简单地写为while fishing == True:


您实际上有一个错误。 while fishing:应该是fishing == False


通过仅检查第一个字母,可以将fishing = False写得更“宽容”(但不太准确):

if answer.lower().startswith("n"):


现在输入也可以使用“ nope”。但是,是否要执行此行为是另外一个故事。如果您还有其他要求“ n”作为第一个字母的答案,那么显然这会破坏事情。

评论


\ $ \ begingroup \ $
关于钓鱼时:假设,通过用户输入或其他方式,钓鱼是字符串,那么钓鱼时始终为True。钓鱼为真时书写安全吗?
\ $ \ endgroup \ $
–niko
19年4月14日在6:32

\ $ \ begingroup \ $
if answer.lower()[0] ==“ n”:如果answer为空字符串,则将中断。如果answer.lower()。startswith('n'):更好。 :-)
\ $ \ endgroup \ $
– TreebledJ
19年4月14日在7:20

\ $ \ begingroup \ $
@niko Fishing从未分配用户输入。如果是偶然,则由此产生的任何行为都是不正确的,因此我认为使用True不会有任何帮助。
\ $ \ endgroup \ $
–致癌物质
19年4月14日在13:13

\ $ \ begingroup \ $
@TrebledJ是的,打个招呼。
\ $ \ endgroup \ $
–致癌物质
19年4月14日在13:13

\ $ \ begingroup \ $
@niko不,不要这样做。防御性编码是一个很好的范例,但是它与处理未知输入有关,而不是与您自己的代码元素有关。钓鱼是正确的说法。
\ $ \ endgroup \ $
–亚当·史密斯(Adam Smith)
19年4月15日在15:57

#3 楼

首先,我认为您的用例是进入Python的一种不错的方式,除了其他人已经指出的错误之外,它似乎很快就会变得不可阻挡。

但是,代替简化代码,我建议模块化以及使用__doc__字符串。它将使将来添加功能变得更加容易,并且,如果您愿意,可以使用KivyBlender或其他许多Python开发GUI框架之一来制作完整的应用程序。再加上模块化或抽象化,可以简化意图/用法。


潜入之前的一些注意事项...



这可能是一个好主意吃零食和饮料;我有点冗长,要压缩一些知识,
__bar__的口语是“ dunder bar”,归类为“魔术方法”。
我分享的是不是这样的福音,而是一系列的技巧,我希望有人在我接触Python时向我展示了

...可以回到正轨。


这是一些受您启发的示例代码,显示了您的问题注释中我正在做的事情...

 #!/usr/bin/env python

import time
import random


print_separator = "".join(['_' for _ in range(9)])
__author__ = "S0AndS0"

#
# Functions
#

def question(message):
    """ Returns response to `message` from user """
    return input("{message}? ".format(message = message))


#
# Classes
#

class Gone_Fishing(dict):
    """
    Gone_Fishing is a simple simulation inspired by
    [Python - Fishing Simulator](https://codereview.stackexchange.com/q/217357/197446)

    ## Arguments

    - `fishes`, `dict`ionary such as `{'cod': {'amount': 0, 'chances': [1, 2]}}`
    - `min_chance`, `int`eger of min number that `random.randint` may generate
    - `max_chance`, `int`eger of max number that `random.randint` may generate
    """

    def __init__(self, fishes, min_chance = 1, max_chance = 10, **kwargs):
        super(Gone_Fishing, self).__init__(**kwargs)
        self.update(fishes = fishes,
                    chances = {'min': min_chance, 'max': max_chance})

    @staticmethod
    def keep_fishing(message, expected):
        """ Return `bool`ean of if `response` to `message` matches `expected` """
        response = question(message)
        if not response or not isinstance(response, str):
            return False

        return response.lower() == expected

    @property
    def dump_cooler(self):
        """
        Returns `score`, a `dict`ionary similar to `{'cod': 5, 'tire': 2}`,
        after printing and reseting _`amount`s_ caught
        """
        score = {}
        for fish, data in self['fishes'].items():
            if data['amount'] > 0:
                score.update({fish: data['amount']})
                if data['amount'] > 1 and data.get('plural'):
                    fish = data['plural']

                print("{amount} {fish}".format(**{
                    'fish': fish,
                    'amount': data['amount']}))

                data['amount'] = 0

        return score

    def catch(self, chance):
        """ Returns `None` or name of `fish` caught based on `chance` """
        caught = []
        for fish, data in self['fishes'].items():
            if chance in data['chances']:
                caught.append(fish)

        return caught

    def main_loop(self):
        """
        Asks questions, adds to _cooler_ anything caught, and prints score when finished
        """
        first = True
        message = 'Go fishing'
        expected = 'yes'
        while self.keep_fishing(message, expected):
            time.sleep(1)
            if first:
                first = False
                message = "Keep fishing"

            chances = random.randint(self['chances']['min'], self['chances']['max'])
            caught = self.catch(chances)
            if caught:
                for fish in caught:
                    self['fishes'][fish]['amount'] += 1
                    fancy_fish = ' '.join(fish.split('_')).title()
                    print("You caught a {fish}".format(fish = fancy_fish))
            else:
                print("Nothing was caught this time.")

        print("{0}\nThanks for playing".format(print_separator))
        if True in [x['amount'] > 0 for x in self['fishes'].values()]:
            print("You caught")
            self.dump_cooler
        print(print_separator)


if __name__ == '__main__':
    """
    This block of code is not executed during import
    and instead is usually run when a file is executed,
    eg. `python gone_fishing.py`, making it a good
    place for simple unit tests and example usage.
    """
    gone_fishing = Gone_Fishing(
        fishes = {
            'cod': {'amount': 0, 'chances': [1]},
            'salmon': {'amount': 0, 'chances': [5]},
            'shark': {'amount': 0, 'chances': [9, 10], 'plural': 'sharks'},
            'wild_fish': {'amount': 0, 'chances': [7], 'plural': 'wild_fishes'},
            'old_shoe': {'amount': 0, 'chances': [10, 15], 'plural': 'old_shoes'},
            'tire': {'amount': 0, 'chances': [2, 19], 'plural': 'tires'},
        },
        min_chances = 0,
        max_chances = 20,
    )

    gone_fishing.main_loop()
 


...好吧,那里有些事了,所以随时添加breakpointsprint(something)行来剖析其操作。


这是输出的内容运行上面的脚本可能看起来像

# python gone_fishing.py
Go fishing? 'yes'
You caught a Wild Fish
Keep fishing? 'yes'
Nothing was caught this time.
Keep fishing? 'yes'
You caught a Shark
You caught a Old Shoe
Keep fishing? 'yes'
Nothing was caught this time.
# ... trimmed for brevity
Keep fishing? 'no'
_________
Thanks for playing
You caught
2 sharks
1 tire
2 wild_fishes
1 cod
_________



从顶部开始使用print_separator = "".join(['_' for _ in range(9)])是我在生成重复字符字符串时要使用的方法,因为它很容易做一些通过_-_-_输出"-".join(['_' for _ in range(3)])的东西。


注意未来;请查看此答案的评论,以获取@Izaak van Dongen提出的一些建议。



通过定义一个从内置的dict离子类class继承的类(这就是class Gone_Fishing(dict):行所做的事情),我有点懒惰,因为它允许通过...转储所有保存的状态。

< pre class =“ lang-py prettyprint-override”> print(gone_fishing) # -> {'cod': {'amount': 2, 'chances': [1]}, ...}


...而我正处于获取信息的切线之中...


 print(gone_fishing.main_loop.__doc__)
# Or
# help(gone_fishing.main_loop)
 



...将打印前面提到的__doc__字符串。


...,弄清楚在哪里也可以避免重新发明轮子,这只是随着时间的流逝而逐渐发展起来的。当我发现正在等待解决某些极端情况的内置函数时,我个人选择将其视为扩展自己的词汇表。


将它们与__init__一起使用,以便使用method参数的其他方法能够获取和/或修改self.update()的保存状态;


旁注; self方法是通过对对象执行某些操作来隐式调用的众多方法之一。通过使用class方法在两个__init__之间使用__add__隐式调用+(侧面说明,稍后我将介绍为什么这是Objects而不是__add__),这就是以下与列表配合工作的原因...


 a 


an的那个位代表list_one = [3, 2, 1] list_two = [0, -1, -2] list_one + list_two # -> [3, 2, 1, 0, -1, -2] ,它以裸露的**kwargs离子形式传递东西,您可能遇到的另一种语法是key word arguments,它以裸露的dict参数形式传递东西;使用这种语法可以做到一些奇特的功能,除了说上下文很重要之外,我现在不会介绍。但是,您会发现一些传递未包装字典的示例,例如通过*args通过list传递给format,该提示暗示是传递变量参数名称的好方法。


这是一个您可以通过一些实验来了解这些惯用的东西(并剔除其他人的代码库);它功能强大,因此经常使用,但对您自己的未来也很友善。


print("{amount} {fish}".format(**{...}))的功能是让super(Gone_Fishing, self).__init__(**kwargs)从自己的Gone_Fishing中调用classdict ...确实有点令人费解,所以花了几秒钟来解压缩......

 __init__ 


...可以在不引起意图混淆的情况下从__init__内调用method,例如,让class SomeThing(dict): def __init__(self, an_argument = None, **kwargs): super(SomeThing, self).__init__(**kwargs) self.update({'an_argument': an_argument}) 仍作为self.update()离子源运行。分配SomeThing.___init__而不引起错误,应该使用SomeThing来允许Python执行它的巫术,并找出继承dict的对象来负责这些自变量。 something = SomeThing(spam = 'Spam'),它的意思是什么,但我在这里不再赘述;某种程度上已经在数学堆栈上关于图形建模和优先级的问题进行了讨论。

super(SomeThing, self).__init__(**kwargs)和其他class是表示特殊使用class SomeThing(dict, Iterator)的方式。对于@staticmethod,它们的操作类似于decorators属性,例如...

 method 


...但只能设置为未设置,这使它们成为隐藏有关property的动态或半私有属性的好地方。

对于Object,它们未通过引用到class Test_Obj: pass o = Test_Obj() o.foo = 'Foo' print(o.foo) # -> Foo ,因此无法轻松访问或修改保存的状态,但无需初始化即可更轻松地使用它们,因此其操作类似于常规函数,例如...

 Object 




通常,我使用它们来使预期用法更加明确,但这并不是说您不会迷失仅用于装饰staticmethod的大量选项。 br />

注意未来;正如@MaartenFabré所指出的那样,我确实在使用self装饰器方面有些多余,很好地抓住了它,现在将作为responses = [] responses.append(question("Where to")) print("I heard -> {response}".format(response = responses[-1])) for _ in range(7): responses.append(question("... are you sure")) print("I heard -> {response}".format(response = responses[-1])) print("Okay... though...") ing时被带走的一个例子。

通常我当我的类不关心其内部状态但又不足以保证它是自己的文件时,请使用.format() s,这是非常极端的情况,通常,这意味着我应该将它们拆分为一个组织类似功能的文件。希望最近的编辑现在看起来更适合将来的读者使用。



f stringsmethod之间的那个位,当展开时,我想您会很喜欢,它将返回staticmethoddecorat位于每次迭代的顶部,基于询问用户一个问题并将他们的响应与预期相比较。

staticmethod的功能是可以使用main_loop掩盖数据的,我建议不要过分看重'em',而应在不使代码可读性降低的情况下尝试利用'em。同样不要太拘泥于智能,因为methodwhile self.keep_fishing(message, expected)或其他许多库之一将更快地执行类似任务。


True之后发生的事情来自文档字符串...


Python新手的注释;确保您可以称呼他们为“ dunder docs”,而那些知道的人也会知道您在说什么,但是他们也很可能会嘲笑您,并且如果在听众喝酒的时间计时说“ dundar doc string”可能会造成混乱后果...所以,当谈论Python代码时,“ pro-tip”,callem“ doc字符串”为False if True in [x['amount'] > 0 for x in self['fishes'].values()] y ;-)


 list comprehensions 


...以及如何解析上面的内容可能需要花费一些文字来进行完整的堆栈跟踪,但是要点是numpypandas,您甚至可以具有重叠的整数,例如。里面有一个if __name__ == '__main__':的一个super可能是...

 class 


...虽然


未来的注意事项;我已经对代码进行了调整,以实现值的重叠和返回多个结果。可能有更好的方法,但这现在也是迭代开发的一个示例。




当您弄清楚gone_fishing = Gone_Fishing(fishes = { 'cod': {'amount': 0, 'chances': [1]}, 'salmon': {'amount': 0, 'chances': [2]}, 'shark': {'amount': 0, 'chances': [3], 'plural': 'sharks'}, 'wild_fish': {'amount': 0, 'chances': [4], 'plural': 'wild_fishes'}, 'old_shoe': {'amount': 0, 'chances': [5, 6], 'plural': 'old_shoes'}, 'tire': {'amount': 0, 'chances': [7, 8], 'plural': 'tires'}, }) 如何成为其中的可选键值对时嵌套的字典中,您将开始在其他代码中看到类似的内容(至少这是我一直无法看到的那些内容之一),但是请尽量不要弄乱该技巧,否则我认为这是不言而喻的使用目的。


我未分配的参数chanceslist,就像带有sharkold_shoe一样,可以类似地更新,例如...

 gone_fishing['fishes']['shark']['chances'].append(5)
 


...尽管初始化新行程看起来像...

 plural 


...举例说明,您应该避免对自己的代码进行操作,明智的选择是,交换单词不会从未来的自我或其他方面赢得任何要点开发人员。


当然,还有更多的改进空间,例如。将min_chance减去,然后添加到max_chance或类似结构中;只是一个开始。但这只是暴露了使用面向对象编程来组织问题空间的快速n-dirty方法。

希望代码多了一些抽象性,这表明您正在处理看起来更复杂的事情复杂可以简化使用和将来的使用。如果您要从您的学习项目中获得更多收益,请保持联系。

评论


\ $ \ begingroup \ $
欢迎使用代码审查!我认为您对这里的第一个答案很满意。保持下去!
\ $ \ endgroup \ $
– AlexV
19年4月13日在8:01

\ $ \ begingroup \ $
很多很好的建议,但我想指出一点:print_separator =“” .join(['_'代表range(9)中的_]]可以简化为:print_separator ='_' * 9
\ $ \ endgroup \ $
– shellster
19年4月13日在23:06



\ $ \ begingroup \ $
print(“ {amount} {fish}”。format(** {'fish':fish,'amount':data ['amount']})))-为什么不进行print(“ {amount} { fish}“。format(fish = fish,amount = data ['amount']))?
\ $ \ endgroup \ $
–kevinsa5
19年4月14日在3:20

\ $ \ begingroup \ $
@ S0AndS0这显然是一个很小的nitpick,但是即使那样,也最好使用“-”。join('_'* 3)中的一个(限制:下划线必须为单个字符并构建中间字符串) ),“-”。join('_'代表range(3)中的_),“-”。join(itertools.repeat('_',3)),取决于您是否喜欢itertools。确实没有必要在内存中构造中介列表。
\ $ \ endgroup \ $
–伊扎克·范·东恩(Izaak van Dongen)
19年4月14日在17:41

\ $ \ begingroup \ $
如果您真的想使用类(对吗?),我希望有一个关于鱼类,池塘,费雪甚至游戏的类。不是这样所有这些静态方法都是类上不需要的,但应放在模块名称空间中。您会使许多简单的事情变得更加复杂。我建议您将这段代码作为一个单独的问题发布,然后您将获得有关如何改进此问题的一些技巧。
\ $ \ endgroup \ $
–马丁·法布雷(MaartenFabré)
19年4月15日在8:59

#4 楼

这是使用字典的另一种改进。当前,您的所有数据都经过硬编码并分布在代码中的某个位置。如果您想添加另一条鱼,则必须添加变量f,扩展random.randint(这样不会减少任何机会),最后将其添加到if条件和打印中。

仅添加另一条鱼就需要很多工作。相反,我建议使用可能的捕鱼结果及其被捕的机会的字典。然后,您可以将其与random.choices一起使用,后者带有weights参数,详细说明了概率。所有的鱼都有相同的概率,什么也没有得到,是任何一条鱼的概率都为两倍。 />
每当需要计数时,使用random.choices可能是一个好主意。它基本上像字典一样工作,并且具有一个不错的功能,即假定所有元素的计数都为零。

在Python 3.6中,引入了一种格式化字符串的新方法fishing

pond = {'cod': 1, 'salmon': 1, 'shark': 1, 'wildfish': 1, 'nothing': 2}


评论


\ $ \ begingroup \ $
为使行为与原始代码相同,没有机会3
\ $ \ endgroup \ $
–神鹰
19年4月14日在20:42

\ $ \ begingroup \ $
@Vaelus randrange不包含结尾,因此产生1到6(含)的值,因此仅5和6不产生任何值。
\ $ \ endgroup \ $
–地狱
19年4月14日在20:45

\ $ \ begingroup \ $
哎呀,你是对的。使用您的方法的另一个原因。
\ $ \ endgroup \ $
–神鹰
19年4月14日在23:36

#5 楼

除其他答案外,您还可以利用python词典:

a = b = c = d = e = 0
...
else:
    t = random.randrange(1, 7)
    if t == 1:
        a += 1
        print("You caught a cod!")
    elif t == 2:
        b += 1
        print("You caught a salmon!")
    elif t == 3:
        c += 1
        print("You caught a shark!")
    elif t == 4:
        d += 1
        print("You caught a wildfish!")
    elif t >= 5:
        e += 1
        print("You caught nothing!")


成为:

caught_fish = {
    'cod': 0,
    'salmon': 0,
    'shark': 0,
    'wildfish': 0,
    'nothing': 0,
}
...
else:
    t = random.randrange(1,7)
    # clamp 't' to dictionary size
    if t > len(caught_fish):
        t = len(caught_fish)
    # pick a type of fish from the list of keys of 'caught_fish' using index 't'
    type_of_fish = list(caught_fish)[t - 1]
    # update the dictionary
    caught_fish[type_of_fish] += 1
    # print what type of fish was caught, or if no fish was caught
    article = 'a ' if type_of_fish != 'nothing' else ''
    print("You caught {}{}!".format(article, type_of_fish))