1

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)

partben
  • 25
  • 9
  • If you need any other code, just ask – partben Dec 17 '15 at 13:25
  • 7
    You **are** calling the functions, and storing the results of those calls. What did you expect to happen instead? – Martijn Pieters Dec 17 '15 at 13:26
  • You call them in the assignment... – Idos Dec 17 '15 at 13:26
  • I wanted to save the versions of the function attack, as those variables – partben Dec 17 '15 at 13:27
  • You may have `function` and `class` a little confused - `def attack` defines a function, which is called wherever you write `slashp=attack(..)` or whatever. A `class` on the other hand, could save off some variable, and then the `AttackType` class could have an `attack` method – dwanderson Dec 17 '15 at 13:28
  • So that I can, fore example, just call kick, and than have that call attack("kick",1,1,1,player,boss) – partben Dec 17 '15 at 13:28
  • From a design perspective, `kick` ought to be something the `player` can do to the `boss` OR the `boss` can do to the `player`; so a `KickAttack` class (that holds `minmult`, `maxmult`, `crit`), that has an `attack(attackee, atacker)` method – dwanderson Dec 17 '15 at 13:31

4 Answers4

5

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
1

This is what functools.partial is for.

Return a new partial object which when called will behave like func called with the positional arguments args and keyword arguments keywords. If more arguments are supplied to the call, they are appended to args. If additional keyword arguments are supplied, they extend and override keywords.

https://docs.python.org/2/library/functools.html#functools.partial

Sample usage:

import functools
slashp = functools.partial("slash",1,2,5,player,boss)
slashp()  # actual call 
Łukasz Rogalski
  • 22,092
  • 8
  • 59
  • 93
0

Wrapp your funtion into a lambda:

slashp=lambda : attack("slash",1,2,5,player,boss)

or use partial:

slashp=partial(attack, name="slash",minmultiplier=1,maxmultiplier=2,critchance=5,attacker=player,attackee=boss)
Netwave
  • 40,134
  • 6
  • 50
  • 93
  • This isn't recommended practice, though. Define a function named `slashp` that calls `attack`, since that's what `slashp` is. – chepner Dec 17 '15 at 13:32
  • @chepner, it is actually a good practice, you are just wrapping your function to take predefined values – Netwave Dec 17 '15 at 13:33
  • @DanielSanchez - based on how the OP wants to use this, they need classes instead; `lambda`, while it would work, has poor readability, especially since it will come up again and again (for all the different attack types, etc) – dwanderson Dec 17 '15 at 13:36
  • @dwanderson, you dont call a lamba, but bind it to a name so the readability is ok in my opinion. – Netwave Dec 17 '15 at 13:38
0

Classes would seem a better fit, and provide more flexibility in the future

class AttackType(object):
    def __init__(name, minmult, maxmult, crit):
        self.name = name
        self.minmult = minmult
        self.maxmult = maxmult
        self.crit = crit
    def attack(self, attackee, attacker):
        // all your logic here

kick = AttackType("kick", 1, 1, 1)
##... later
kick(player, boss)
dwanderson
  • 2,775
  • 2
  • 25
  • 40
  • I think this is pretty too much, you are creating a whole object for calling a method, at least implement the __call__ method. – Netwave Dec 17 '15 at 13:40