Search code examples
pythonfunctiontext-based

My function in python is being called, even though I'm only defining it


I have a function called attack:

def attack(name,minmultiplier,maxmultiplier,critchance,attacker,attackee):
    print(attacker[0],"used",name)
    multiplier=random.randint(minmultiplier,maxmultiplier)
    crit=random.randint(critchance,100)
    if crit==100 and ((attacker[2]*multiplier*2)-attackee[3]) > 0:
        attackee[1]=attackee[1]-((attacker[2]*multiplier*2)-attackee[3])
    elif ((attacker[2]*multiplier)-attackee[3]) > 0:
        attackee[1]=attackee[1]-((attacker[2]*multiplier)-attackee[3])
    else:
        print("You fail to hit",attackee[0])
    print(attackee[0],"'s health after",attacker[0],"'s attack is",attackee[1])

And I am making a few actual attacks for bosses and the player here:

boss=["Greed",1000,10,10,1]
slashp=attack("slash",1,2,5,player,boss)
slashb=attack("slash",1,2,5,boss,player)
kick=attack("kick",1,1,1,player,boss)
aiattacklist=[slashb]
attacknamelist=["slash","kick"]
attackfunclist=[slashp,kick]

Even though I am only saving these versions as variables, they are still being called:

template used slash
You fail to hit Greed
Greed 's health after template 's attack is 1000
Greed used slash
template 's health after Greed 's attack is 58
template used kick
You fail to hit Greed
Greed 's health after template 's attack is 1000

Is this something that python always does, or am I doing something wrong, because I don't want these to be called (sorry if I didn't use the correct terminology, I am kind of new)


Solution

  • You are calling the functions here:

    slashp=attack("slash",1,2,5,player,boss)
    slashb=attack("slash",1,2,5,boss,player)
    kick=attack("kick",1,1,1,player,boss)
    

    You are storing the return value there, not the function.

    If you wanted to store some predefined arguments, either use another function to wrap the call, use a lambda (which is basically a simplified form of creating a function), or use functools.partial() to predefine some arguments and store a new callable for those.

    Using a lambda would look like this:

    shlashp = lambda player, boss: attack("slash", 1, 2, 5, player, boss)
    shlashb = lambda player, boss: attack("slash", 1, 2, 5, boss, player)
    kick = lambda player, boss: attack("kick", 1, 1, 1, player, boss)
    

    This assumes that you still want to specify the player and the boss later on when calling these functions. You'd call kick(player, boss), for example.

    Using functools.partial() is not as suitable here, because you are swapping the boss and player arguments around; you'd just define a slash variable and pass in the boss and the player arguments in the right order:

    from functools import partial
    
    slash = partial(attack, 'slash', 1, 2, 5)
    kick = partial(attack, 'kick', 1, 1, 1)
    

    Calling either slash or kick would add on any extra arguments, so slash(player, boss) calls the function with those two arguments added on to the ones you already defined.

    All of this makes the assumption you want to be able to manage multiple players and bosses. If your player and boss variables are globals (perhaps there is only ever one player and one boss to fight), then you'd just pass those in when defining the lambda or the partial and you don't pass in extra arguments. For example:

    slashp = partial(attack, 'slash', 1, 2, 5, player, boss)
    slashb = partial(attack, 'slash', 1, 2, 5, boss, player)
    kick = partial(attack, 'kick', 1, 1, 1, player, boss)
    

    and to have the player kick the boss, you'd just call kick().

    The difference between a partial object and a lambda is that you can introspect the partial object; you can easily see what arguments you defined to be always pass in:

    >>> from functools import partial
    >>> def attack(*args): return args
    ...
    >>> demo = partial(attack, 'kick', 1, 2, 5)
    >>> demo.args
    ('kick', 1, 2, 5)
    >>> demo()
    ('kick', 1, 2, 5)
    >>> demo('player1', 'boss2')
    ('kick', 1, 2, 5, 'player1', 'boss2')
    

    A partial object can not be used as a method on a class, a function object can. Use a functools.partialmethod() object if you need to use this feature on a class.