You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 17:02:09