assigning values in __init__

Steven D'Aprano steve at REMOVE.THIS.cybersource.com.au
Mon Nov 6 22:49:17 EST 2006


On Mon, 06 Nov 2006 16:57:58 -0500, John Salerno wrote:

> Let's say I'm making a game and I have this base class:
> 
> class Character(object):
> 
>      def __init__(self, name, stats):
>          self.name = name
>          self.strength = stats[0]
>          self.dexterity = stats[1]
>          self.intelligence = stats[2]
>          self.luck = stats[3]
> 
> Is this a good way to assign the values to the different attributes? 
> Should 'stats' be a list/tuple (like this), or should I do *stats instead?


Whenever possible, think about writing self-documenting code:

def __init__(self, name, strength, dexterity, intelligence, luck):
    self.name = name
    self.strength = strength
    # etc.

seems perfectly acceptable to me (if a tad verbose, but that isn't a big
deal -- write once, never touch again).

The problem with function signatures like these:

def __init__(self, name, stats):
def __init__(self, name, *stats):

is that the format of stats is left unstated. Is it (luck, strength,
intelligence) or (strength, intelligence, luck) or (wisdom, charisma,
power, health) or something else? You shouldn't need to read the code
line by line to find out, and relying on documentation risks having the
code and docs get out of sync.

If you expect the stats to be passed as a single tuple, you can still make
it explicit: just wrap the field names within brackets.

def __init__(self, name, (strength, dexterity, intelligence, luck) ):

> I'm trying to think ahead to when I might want to add new attributes, 

If this is likely, you could do something like this:

def __init__(self, name, **stats):
    self.name = name
    self.__dict__.update(stats)

Adding extra attributes is fine, since they will just be ignored, but what
if the caller adds an attribute "itnelligence" (instead of intelligence)?
You're now writing lots of code like this:

def save_intelligence(self, threshold):
    """Roll a saving throw against intelligence"""
    try:
        return roll(self.intelligence) > threshold
    except AttributeError:
        # character has no intelligence, so always fails
        return False

Yes, you can make that easier with currying, decorators etc. but why not
make sure your characters have the required attributes in the first place?

One way of doing that would be to add default values for the required
attributes in the class, and let instances inherit those defaults from the
class.


> and I want to make sure this doesn't get crazy with individual 
> parameters instead of just the one list.

If you've got that many character attributes, I'm guessing that your game
will be a tad hard to play :-)

If you have more than a half-dozen or ten character attributes, you could
consider encapsulating them in some way. E.g. group-related attributes and
pass them as tuples:

power => (constitution, health, anaerobic_strength, aerobic_strength)
intelligence => (IQ, wisdom, dexterity, book_learning, street_wisdom)
charisma => (beauty, chutzpah, attractiveness, persuasiveness)
senses => (vision, hearing, smell, telepathy, empathy, feeling, spacial)
others => (luck, determination, laziness, attention_to_detail)

You could then roll against power, say, by giving a set of weights:

character.roll('power', (0.0, 0.0, 0.9, 0.1))

gives anaerobic strength a weighting of 90% and aerobic 10%.

But again, I think that if your roll-playing game needs to have such
fine-grained attributes, I think it will be too complex to be fun.



> Or maybe there's some way to loop through two lists (the stats and the 
> attributes) and assign them that way? I was thinking of a nested for 
> statement but that didn't seem to work.

def __init__(self, stats):
    names = ['strength', 'dexterity', 'intelligence', 'luck']
    for name, stat in zip(names, stats):
        setattr(self, name, stat)



-- 
Steven.




More information about the Python-list mailing list