Python无条件语句实现石头剪刀布遇TypeError,求类型推断方案
无条件语句实现石头剪刀布:问题与修复
本人拥有C#开发背景,学习任何OOP语言时,常会参考曾遇到的一道Smalltalk面试题:如何在不使用任何条件语句的情况下实现石头剪刀布游戏?
以下是我的实现代码,但运行时出现错误,想请教如何让基类推断类型并接受任意子类作为参数。
game_piece.py
"""Game Piece Class represenets the base game piece""" from __future__ import annotations class GamePiece: """Base class for all game pieces""" def __init__(self) -> None: pass def __repr__(self) -> str: return "<GamePiece>" def play(self, game_piece: GamePiece) -> bool: """Play method from base class for all game pieces to override. This method will call the game piece's specific 'do you beat' method :param game_piece: The user's chosen game piece. :return: bool """ return \ game_piece.do_you_beat_paper() | \ game_piece.do_you_beat_rock() | \ game_piece.do_you_beat_scissors() def do_you_beat_rock(self) -> bool: """Every game piece is asked if they can beat a other game pieces. Returns a true if it can. Otherwise, returns a false. :return: bool """ return False def do_you_beat_paper(self) -> bool: """Every game piece is asked if they can beat a other game pieces. Returns a true if it can. Otherwise, returns a false. :return: bool """ return False def do_you_beat_scissors(self) -> bool: """Every game piece is asked if they can beat a other game pieces. Returns a true if it can. Otherwise, returns a false. :return: bool """ return False
rock.py
"""Represents the Rock Game Piece""" from game_piece import GamePiece class Rock(GamePiece): """This class represents the Rock Game Piece""" def __repr__(self) -> str: return "<Rock>" def play(self, game_piece: GamePiece) -> bool: """Call this method to have the game pieces play each other. It'll return a true if the game piece is a Paper. Otherwise return false. :param game_piece: Represents the game piece that it's playing against :returns: bool """ return game_piece.do_you_beat_rock() def do_you_beat_scissors(self) -> bool: """When the opponent game piece is a pair of scissors it will call this method on the Rock opponent piece and return true to demonstrate that it can beat a pair of scissors. :return: bool """ return True
paper.py
"""Represents the Paper Game Piece""" from game_piece import GamePiece class Paper(GamePiece): """This class represents the Paper Game Piece""" def __repr__(self) -> str: return "<Paper>" def play(self, game_piece: GamePiece) -> bool: """Call this method to have the game pieces play each other. It'll return a true if the game piece is Scissors. Otherwise return false. :param game_piece: Represents the game piece that it's playing against :returns: bool """ return game_piece.do_you_beat_paper() def do_you_beat_rock(self) -> bool: """When the opponent game piece is a rock it will call this method on the Paper opponent piece and return true to demonstrate that it can beat a rock. :return: bool """ return True
scissors.py
"""Represents Scissors Game Piece""" from game_piece import GamePiece class Scissors(GamePiece): """This class represents the Paper Game Piece""" def __repr__(self) -> str: return "<Scissors>" def play(self, game_piece: GamePiece) -> bool: """Call this method to have the game pieces play each other. It'll return a true if the game piece is Paper. Otherwise return false. :param game_piece: Represents the game piece that it's playing against :returns: bool """ return game_piece.do_you_beat_scissors() def do_you_beat_paper(self) -> bool: """When the opponent game piece is a Paper it will call this method on the Scissors opponent piece and return true to demonstrate that it can beat a Paper. :return: bool """ return True
main.py(原错误版本)
"""The main calling program for the game Rock, Paper, Scissors""" import random from typing import Any import click from game_piece import GamePiece from paper import Paper from rock import Rock from scissors import Scissors @click.group() def cli(): """Required by click""" @click.command() @click.option("--selection", prompt="Please select: [R]ock, [P]aper or [S]cissors", help="The user's selection of either \ [R]ock, [P]aper or [S]cissors.") def shoot(selection: str) -> None: """The call to 'Shoot' based on the rules :param selection: The user's choice :return: None """ game_piece_list = {"R": Rock, "P": Paper, "S": Scissors} user_selected_game_piece = game_piece_list[selection.upper()] mach_selected_game_piece: Any = random.choice( list(game_piece_list.items())) print(f"You selected {selection} which is {user_selected_game_piece}") does_user_win = user_selected_game_piece.play(mach_selected_game_piece) print(f"Can {user_selected_game_piece} beat {mach_selected_game_piece}") cli.add_command(shoot) if __name__ == "__main__": cli()
运行错误
TypeError: Rock.play() missing 1 required positional argument: 'game_piece'
问题分析与修复
1. 错误根源
user_selected_game_piece存储的是类(如Rock)而非类的实例,调用play方法时,是通过类调用实例方法,导致缺少self参数。mach_selected_game_piece通过random.choice(list(game_piece_list.items()))得到的是键值对(如("R", Rock)),不是GamePiece的实例,无法作为play方法的参数。
2. 修复后的main.py
"""The main calling program for the game Rock, Paper, Scissors""" import random import click from game_piece import GamePiece from paper import Paper from rock import Rock from scissors import Scissors @click.group() def cli(): """Required by click""" @click.command() @click.option("--selection", prompt="Please select: [R]ock, [P]aper or [S]cissors", help="The user's selection of either [R]ock, [P]aper or [S]cissors.") def shoot(selection: str) -> None: """The call to 'Shoot' based on the rules :param selection: The user's choice :return: None """ game_piece_classes = {"R": Rock, "P": Paper, "S": Scissors} # 创建用户选择的游戏棋子实例 user_piece: GamePiece = game_piece_classes[selection.upper()]() # 随机选择机器的游戏棋子类并创建实例 machine_piece_class = random.choice(list(game_piece_classes.values())) machine_piece: GamePiece = machine_piece_class() print(f"You selected {selection.upper()} which is {user_piece}") print(f"Machine selected {machine_piece}") # 判断用户是否获胜 user_wins = user_piece.play(machine_piece) if user_wins: print("You win!") elif machine_piece.play(user_piece): print("Machine wins!") else: print("It's a tie!") cli.add_command(shoot) if __name__ == "__main__": cli()
3. 类型推断与子类兼容
Python中,只要子类继承自GamePiece,就可以直接作为play方法的参数——因为GamePiece类型注解会自动接受自身及所有子类实例,类型检查器(如mypy)也能识别这种兼容关系,无需额外配置。
如果需要更明确的类型约束,可以使用TypeVar:
from typing import TypeVar GamePieceType = TypeVar("GamePieceType", bound=GamePiece) def play(self, game_piece: GamePieceType) -> bool: # 方法实现
但在当前场景下,直接使用GamePiece作为参数类型已经足够满足需求。
内容的提问来源于stack exchange,提问作者OneClutteredMind
相关产品推荐
相关产品推荐

