Home > Back-end >  Python Structural pattern matching - pass Object to case statement
Python Structural pattern matching - pass Object to case statement

Time:05-29

this is my code

class BasePlayer:
    @staticmethod
    def walk(direction):
        print(f"I run into {direction}")

class Archer(BasePlayer):
    @staticmethod
    def shoot():
        print("shoot")

class Wizard(BasePlayer):
    @staticmethod
    def cast_spell():
        print("Spell casted")

def play(expr):
    match expr.split():
        case [("Archer" | "Wizard") as player, "walk", ("north" | "south" | "west" | "east") as direction]:
            Archer.walk(direction)
        case ["Archer", "shoot"]:
            Archer.shoot()
        case _:
            raise Exception("Command not working...")

play("Archer walk west")

That works fine so far, but I want in the first case statement, I want so do it more generic like this:

    case [(Archer | Wizard) as player, "walk", ("north" | "south" | "west" | "east") as direction]:
        player.walk(direction)

However I can not use Objects in the case statements (pattern does not bind Archer). If I leave it as a string, player will not be the class, but also just a string.

Is there a way to make this work?

CodePudding user response:

First off, expr is of type str and expr.split() produces a list of str. You will need to change that part of the match statement to a function that will produce the type you want to actually match on.

Second part of the problem you will (or probably have) run into with your version of the generic statement is this particular issue, where the bare name is assumed to be a variable to make assignment to, so a workaround is required (see the answer in that thread for more details). A demonstration of the fault follows::

>>> match some_expression:
...     case [(Archer | Wizard) as player, "walk", ("north" | "south" | "west" | "east") as direction]:
...         player.walk(direction)
... 
  File "<stdin>", line 2
SyntaxError: name capture 'Archer' makes remaining patterns unreachable

As a quick and dirty answer, I am going to do the lazy thing and assign the subclasses as attributes to an empty class (to emulate importing a module that contain these playable types, e.g. the unit module for playable units). The core idea of what should be done to achieve your desired objective might look something like this:

class unit():
    "pretend this is a module"

unit.Wizard = Wizard
unit.Archer = Archer

def parse(expr):
    # Parse the incoming expression to [BasePlayer, str, str, ...]
    # Defaults to BasePlayer for unknown unit type.
    r = expr.split()
    r[0] = getattr(unit, r[0], BasePlayer)
    return r

def play2(expr):          
    match(parse(expr)):  # use the above parse function to parse the expression
        case [(unit.Archer | unit.Wizard) as player, "walk", ("north" | "south" | "west" | "east") as direction]:
            player.walk(direction)
        case [unit.Archer as player, "shoot"]:
            player.shoot()
        case _:
            raise Exception("Command not working...")

Trying out the play2 function:

>>> play2('Archer walk west')
I run into west
>>> play2('Wizard walk west')
I run into west
>>> play2('Archer shoot')
shoot
>>> play2('Wizard shoot')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in play2
Exception: Command not working...
  • Related