简体   繁体   中英

In Python, use a method both as instance and class method

I'm writing a program which plays Tic Tac Toe and has various versions of ComputerPlayer , such as the RandomPlayer and THandPlayer :

class RandomPlayer(ComputerPlayer):
    def __init__(self, mark):
        super(RandomPlayer, self).__init__(mark=mark)

    def get_move(self, board):
        moves = board.available_moves()
        if moves:   # If "moves" is not an empty list (as it would be if cat's game were reached)
            return moves[np.random.choice(len(moves))]    # Apply random select to the index, as otherwise it will be seen as a 2D array

class THandPlayer(ComputerPlayer):
    def __init__(self, mark):
        super(THandPlayer, self).__init__(mark=mark)

    def get_move(self, board):
        moves = board.available_moves()
        if moves:   # If "moves" is not an empty list (as it would be if cat's game were reached)
            for move in moves:
                if board.get_next_board(move, self.mark).winner() == self.mark:                         # Make winning move (if possible)
                    return move
                elif board.get_next_board(move, self.opponent_mark).winner() == self.opponent_mark:     # Block opponent's winning move
                    return move
            else:
                # return moves[np.random.choice(len(moves))]        # This is a repetition of the code in RandomPlayer and is not DRY
                randomplayer = RandomPlayer(mark=self.mark)
                return randomplayer.get_move(board)
                # return RandomPlayer.get_move(board)         # This returns an error as "get_move" is an instance method

The THandPlayer also selects moves at random if no winning move can be made or an opponent's winning move blocked. Right now I am doing this by creating an instance of RandomPlayer and calling get_move on it. This could be made more succinct, however, if get_move could be made such that it can be interpreted both as a class method and an instance method. Is this possible?

EDIT

To simplify the question, suppose we have two classes, RandomPlayer and OtherPlayer , both which have an instance method get_move :

import numpy as np

class RandomPlayer:
    def get_move(self, arr):
        return np.random.choice(arr)

class OtherPlayer:
    def get_move(self, arr):
        if max(arr) > 5:
            return max(arr)
        else:
            randomplayer=RandomPlayer()
            return randomplayer.get_move(arr)

arr = np.arange(4)

otherplayer = OtherPlayer()
print otherplayer.get_move(arr)

Is it possible to use RandomPlayer 's get_move method in OtherPlayer without creating an instance of RandomPlayer ?

It sounds like you're looking for a staticmethod , which has access to neither cls nor self but can be accessed via either:

>>> class Foo:
...     @staticmethod
...     def bar():
...         print('baz')
...         
>>> Foo.bar()
baz
>>> Foo().bar()
baz

A random move is a specific type of move; put a method which generates one in ComputerPlayer ; then both RandomPlayer and THandPlayer can call it as necessary.

class ComputerPlayer(...):
    @staticmethod
    def choose_random_move(moves):
        if moves:
            return moves[np.random.choice(len(moves))]


class RandomPlayer(ComputerPlayer):    
    def get_move(self, board):
        moves = board.available_moves()
        if moves:
            return self.choose_random_move(moves)


class THandPlayer(ComputerPlayer):

    def get_move(self, board):
        moves = board.available_moves()
        for move in moves:
            for mark in [self.mark, self.opponent_mark]:
                if board.get_next_board(move, mark).winner() == mark:
                    return move
        else:
            return self.choose_random_move(moves)

Some extra notes:

  • If your __init__ method doesn't do anything except call super and pass along the exact same arguments, don't implement it; just let the inherited method be called directly.

  • The two checks for a winner can be refactored.

  • choose_random_move doesn't necessarily need to be a static method; you can keep it as an instance method with a default implementation that ignores any player-specific information in choosing a move. Derived classes can override the method if they like.

(This is an alternative to my other answer, using a different abstraction.)

A random move isn't something associated with a player as much as it is something associated with a board; it's like board.available_moves , but returns a single move instead of all moves.

 class Board(...):
     # Given how often this is called by or before
     # random_move(), it would be smart to implement
     # some kind of caching so that the available
     # moves don't have to be recalcuated for the same board
     # state every time it is called.
     def available_moves(self):
         ...

     def random_move(self):
         moves = self.available_moves()
         if moves:
             return moves[np.random.choice(len(moves))]

class RandomPlayer(ComputerPlayer):

    def get_move(self, board):
        return board.random_move()

class THandPlayer(ComputerPlayer):
    def get_move(self, board):
        moves = board.available_moves()
        if moves:
            for move in moves:
                if board.get_next_board(move, self.mark).winner() == self.mark:
                    return move
                elif board.get_next_board(move, self.opponent_mark).winner() == self.opponent_mark:
                    return move
            else:
                return board.random_move()

For the sake of completeness, here is my implementation of the solution suggested by deceze , in which I also followed chepner 's suggestion to refactor the two Boolean statements:

class RandomPlayer(ComputerPlayer):
    def __init__(self, mark):
        super(RandomPlayer, self).__init__(mark=mark)

    @staticmethod
    def get_move(board):
        moves = board.available_moves()
        if moves:   # If "moves" is not an empty list (as it would be if cat's game were reached)
            return moves[np.random.choice(len(moves))]    # Apply random selection to the index, as otherwise it will be seen as a 2D array

class THandPlayer(ComputerPlayer):
    def __init__(self, mark):
        super(THandPlayer, self).__init__(mark=mark)

    def get_move(self, board):
        moves = board.available_moves()
        if moves:
            for move in moves:
                if THandPlayer.next_move_winner(board, move, self.mark):
                    return move
                elif THandPlayer.next_move_winner(board, move, self.opponent_mark):
                    return move
            else:
                return RandomPlayer.get_move(board)         

    @staticmethod
    def next_move_winner(board, move, mark):
        return board.get_next_board(move, mark).winner() == mark

A static method is used both to default to the random player and to refactor the Boolean statements.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM