【问题标题】:How can I make this code Pythonic我怎样才能使这个代码 Pythonic
【发布时间】:2010-09-16 05:00:35
【问题描述】:

所以我有这个对象的代码。那个物体是你可以在石头剪刀布游戏中做出的动作。 现在,为了方便书写和查看,对象需要同时是整数(用于匹配协议)和字符串。

class Move:
    def __init__(self, setMove):
        self.numToName = {0:"rock", 1:"paper",2:"scissors"} 
        self.nameToNum = dict(reversed(pairing) for pairing in self.numToName.items())
        if setMove in self.numToName.keys():
            self.mMove=setMove
        else:
            self.mMove=self.nameToNum.get(setMove) #make it to a number

    def defeats(self):
        return Move((self.mMove-1)%3)
    def losesTo(self):
        return Move((self.mMove+1)%3)
    def tiesWith(self):
        return self

    #Operator overloading
    def __eq__(A,B):
        return A.mMove==B.mMove
    def __gt__(A,B):
        return A.defeats(B)
    def __lt__(A,B):
        return A.losesTo(B)
    def __ge__(A,B):
        return A>B or A==B
    def __le__(A,B):
        return A<B or A==B

    def __str__(self):
        return self.numToName.get(self.mMove);

    def __int__(self):
        return self.mMove;

现在我对 python 有点陌生,来自 C 和 Java 背景。 python 中的一件大事是只有一种正确的方法可以做某事。 另一件事是不用担心类型。 我很明确地担心这里的类型。

所以我不确定处理这些对象的正确方法是什么。 目前我有一个对象,它是任何 3 种类型之一(或更多,但我不确定那会做什么) 也许相反我应该使用不同类的对象?并让他们成为单身人士? 此外,我的对象目前在创建后是可修改的,这在我看来是一件坏事。

那么这段代码是 Pythonic,我怎样才能让它更优雅? (我认为这是一个很好的例子,可以帮助我弄清楚什么是好的 Python 代码。对不起,如果它看起来有点开放式结束)

【问题讨论】:

  • 如果您想了解更多关于实际问题/游戏的信息:progcomp.ucc.asn.au/FrontPage 这些动作是一件小事,我只是随机认为我可能想创建一个类型,作为一种更好的压制方式他们。

标签: oop coding-style python


【解决方案1】:

对我来说,“pythonic”代码的概念实际上归结为这样一种想法,即一旦您了解要解决的问题,代码几乎可以自己编写。在这种情况下,不用担心玩家、游戏、投掷等更深层次的抽象,你会遇到以下问题:有一定数量的移动类型,每种类型都有一个名称,并设置了哪些移动击败其他移动的规则移动,您需要找到一种方法来定义移动并找出比较中哪个移动获胜。

当我阅读你的代码时,我并没有立即看到这个问题,我看到很多额外的想法进入了代码本身,寻找类型表示,做算术技巧,并且通常将问题强制到代码框架中,而不是反过来。所以我建议如下:


class Move:
  TYPES = ['rock', 'paper', 'scissors']
  BEATS = {
    'rock': ['scissors'],
    'paper': ['rock'],
    'scissors': ['paper']
  }

  def __init__(self, type):
    if type not in self.TYPES:
      raise Exception("Invalid move type")
    self.type = type

  def __str__(self):
    return self.type

  def __cmp__(self, other):
    if other.type in self.BEATS[self.type]:
      return 1
    elif self.type in self.BEATS[other.type]:
      return -1
    else:
      return 0

你就完成了。你可以把所有其他的访问器等等都扔进去,但这真的只是锦上添花,核心问题解决了,代码可读、灵活、易于扩展等。这就是我认为“pythonic”的真正含义。

【讨论】:

  • 如果你真的需要一个整数,使用:def __int__(self): return self.TYPES.index(self.type)
【解决方案2】:

嗯,你只有三个可能的动作,对吧?为什么不将它们表示为字符串?看起来你拥有这些数字的唯一原因是用一些“聪明”的数学来实现比较(即哪个打败了哪个),但老实说,我认为这不值得。您真正需要的是一个函数来确定在每个可能的比较中哪个是赢家:

def winner(move0, move1):
    if move0 == move1:
        return None
    elif (move0 == 'rock' and move1 == 'scissors') or \
         (...paper vs. rock...) or \
         (...scissors vs. paper...):
        return 0
    else:
        return 1

我只是将返回值None01 作为示例,您可以使用适合您情况的任何值。

“简单胜于复杂”,Python 之禅第 3 行 ;-)

【讨论】:

  • 如我所说。 “我需要整数来匹配协议”世界上还有其他东西像我一样投掷动作,而我投掷动作,你知道正在对你做什么的机制是这个协议,它是基于围绕这些数字。 The Stings 只是为了方便。
  • 好的,如果它们必须是整数,只需使用整数而不是字符串,即在我的答案中替换 'rock' -> 0 等。它并没有真正改变任何东西。如果你想打印一个字符串表示,对于这么简单的事情,我可能只是创建一个函数来返回与给定动作对应的字符串:def as_string(move): return {0:"rock", 1:"paper",2:"scissors"}[move]
【解决方案3】:

这是一个用语言表达结果的简短版本。

def winner(p1, p2):
    actors = ['Paper', 'Scissors', 'Rock']
    verbs = {'RoSc':'breaks', 'ScPa':'cut', 'PaRo':'covers'}
    p1, p2 = actors.index(p1), actors.index(p2)
    winner, looser = ((p1, p2), (p2, p1))[(1,0,1)[p1 - p2]]
    return ' '.join([actors[winner],
                     verbs.get(actors[winner][0:2] + actors[looser][0:2],
                               'ties'),
                     actors[looser]])

这种结构的好处在扩展到涵盖 Rock、Paper、Scissors、Lizard、Spock 时显而易见

def winner(p1, p2):
    actors = ['Paper', 'Scissors', 'Spock', 'Lizard', 'Rock']
    verbs = {'RoLi':'crushes', 'RoSc':'breaks', 'LiSp':'poisons',
             'LiPa':'eats', 'SpSc':'smashes', 'SpRo':'vaporizes', 
             'ScPa':'cut', 'ScLi':'decapitate', 'PaRo':'covers', 
             'PaSp':'disproves'}
    p1, p2 = actors.index(p1), actors.index(p2)
    winner, looser = ((p1, p2), (p2, p1))[(1,0,1,0,1)[p1 - p2]]
    return ' '.join([actors[winner],
                     verbs.get(actors[winner][0:2] + actors[looser][0:2],
                               'ties'),
                     actors[looser]])

>>> winner("Rock", "Scissors")
'Rock breaks Scissors'
>>> winner("Rock", "Spock")
'Spock vaporizes Rock'
>>> winner("Spock", "Paper")
'Paper disproves Spock'
>>> winner("Lizard", "Scissors")
'Scissors decapitate Lizard'
>>> winner("Paper", "Paper")
'Paper ties Paper'

【讨论】:

  • +1 - 概括的结果非常好。尤其是动词。
【解决方案4】:
mv = {"Scissor":0, "Rock":1, "Paper":2}
def winner(m1, m2):
  result = "Tie" if m1 == m2 else max(m1, m2) if abs(m1 - m2) != (len(mv) - 1) else min(m1, m2)
  return mv.keys()[mv.values().index(result)] if result in mv.values() else result

我写这篇文章是为了证明这个概念:用 5 行代码,几乎没有面向对象,你可以达到规定的结果,纸;岩石;剪刀。

数字/字符串字典。如果您输入数字,您的结果将是获胜字符串的名称。获胜的有效性是顺序的(a "Tie",因为这是一个明显的例子,但真正与玩家一起构建游戏,并且用这种方法一切都是微不足道的。现在,如果你想玩 Paper、Rock、Scissors、Lizard、Spock,我们需要重构。

【讨论】:

  • 哇。我有点喜欢这样,只要我不必在事先不知道它做什么的情况下解释它的作用。 ;)
  • 应该有一种——最好只有一种——明显的方法。虽然这种方式一开始可能并不明显,除非你是荷兰人。
【解决方案5】:

我不确定游戏的抽象程度是否足够好。移动是需要两名玩家参与的事件。换句话说,一个动作不是玩家,玩家也不是一个动作。您对此有何看法:

# notice that the element k+1 defeats element k
THROWS = ['paper', 'scissors', 'rock']

class Player(object):
    def __init__(self, name, throws):
    # name the player
    self.name = name
    # the throws are contained a priori
    self.throws = throws

    def throw(self):
    # a throw uses (and removes) the first element of the throws
    # list
    return self.throw_value(self.throws.pop(0))

    def throw_value(self, what):
    if what in [0,1,2]:
        # if the throw is a legal int, return it
        return what
    if what in THROWS:
        # if the throw is a legal str, return the
        # corresponding int
        return THROWS.index(what)
    # if none of the above, raise error
    raise ValueError('invalid throw')

class Game(object):
    def __init__(self, player_1, player_2):
    # a game has two players
    self.player_1 = player_1
    self.player_2 = player_2

    def go(self, throws=3):
    # a "go" of the game throws three times
    for _ in range(throws):
        print self.throw()

    def throw(self):
    # a throw contains the rules for winning
    value_1 = self.player_1.throw()
    value_2 = self.player_2.throw()

    if value_1 == value_2:
        return 'draw'

    if value_1 > value_2:
        return self.player_1.name

    return self.player_2.name

if __name__ == "__main__":
    juan = Player("Juan", ['rock', 0, 'scissors'])

    jose = Player("Jose", [1, 'scissors', 2])

    game = Game(juan, jose)

    game.go()

【讨论】:

  • 虽然还没有仔细阅读你的代码,但是是的,一个动作不是一个玩家。游戏正在由另一个应用程序运行,玩家可以用任何语言编写。他们通过 stdio 使用数字 0、1、2 与游戏通信。游戏会告诉他们他们赢了还是输了。请参阅:progcomp.ucc.asn.au/FrontPage 我只是创建了一个移动类供我内部使用,用于跟踪其他玩家的行为
  • 我提出的解决方案也适用于这种情况。无论谁移动玩家,“GAME”界面都有效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-26
  • 1970-01-01
  • 2012-09-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多