Search code examples
pythonpython-3.xfunctools

Workaround for Python partial determining parameters upon definition?


I have been working on a card-based combat game to practice Python coding, and I have objects for the players and the cards. I have been using functools.partial to assign an ability to each card object when I create it, and that has been working so far like this:

from functools import partial

class Player(object):
    def __init__(self):
        self.health = 100
    def play_card(self, card):
        card.ability(player=self)

human = Player()
computer = Player()
human.opponent = computer
computer.opponent = human

class Card(object):
    def __init__(self, ability):
        self.ability = ability

def attack(amount, player):
    player.opponent.health -= amount

working_card = Card(partial(attack, 8))
human.play_card(working_card)

This is obviously a vastly simplified version of the code, but it's the important part. When I call human.play_card(working_card), the human Player object executes the function play_card(card=working_card), which then activates the working_card.ability() partial function, initiating the attack(amount=8, player=human) function.

But then I introduce a variable as one of the parameters of the partial function like this:

broken_card = Card(partial(attack, player.health))
human.play_card(broken_card)

The partial function is trying to determine the value of player.health when the Card object is first created, when it does not know what 'player' is. I want it instead to determine the value of player.health when attack(amount, player) is called, which is when 'player' has meaning. I have tried using lambda and placing the attack() function inside the Card object with no success, and I could not find any answers to this sort of question on the Internet. I need some way to work around partial trying to determine player.health when it is first called, if not explicitly than by a restructuring of the way my code's logic works.


Solution

  • You can use lambda instead of partial to defer the evaluation of parameters:

    broken_card = Card(lambda *args, **kwargs: attack(player.health, *args, **kwargs))
    

    Keep in mind that player has to be available in the same scope as where this lambda is defined. however. Since player happens to be the second parameter of attack, you can make lambda make use of the known parameter:

    broken_card = Card(lambda player: attack(player.health, player))