assigning values in __init__

J

John Salerno

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?

I'm trying to think ahead to when I might want to add new attributes,
and I want to make sure this doesn't get crazy with individual
parameters instead of just the one list.

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.
 
L

Larry Bates

John said:
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?

I'm trying to think ahead to when I might want to add new attributes,
and I want to make sure this doesn't get crazy with individual
parameters instead of just the one list.

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.

Sounds like what you should be doing is something like keyword arguments
instead.

class Character(object):
def __init__(self, name, **kwargs):
self.name=name
for key, value in kwargs.items():
setattr(self, key, value)


z=Character('name', strength=10, dexterity=5, intelligence=3, luck=0)

Now you can easily introduce new keyword arguments.

-Larry
 
G

Gerard Flanagan

John said:
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?

How about:

class Character(object):

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

c = Character( "Plato", strength=10, luck=12)

print getattr(c, "strength")
print getattr(c, "luck")

10
12
 
S

Steve Holden

John said:
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?

I'm trying to think ahead to when I might want to add new attributes,
and I want to make sure this doesn't get crazy with individual
parameters instead of just the one list.

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.

If your program deals with 4-element tuples then although you *could*
use *stats in your calls to pass each element of the tuple as a single
argument, that's not really necessary. A way to write the
initializations you want without using indexing is:

class Character(object):

def __init__(self, name, stats):
self.name = name
self.strength, self.dexterity, \
self.intelligence, self.luck = stats

regards
Steve
 
S

Steven D'Aprano

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)
 
B

Ben Finney

John Salerno said:
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?

A tuple is fine, but assumes that there is a logical *sequence* to
these values. If that's true, it should be no problem to assign them as:

class Character(object):
def __init__(self, name, stats):
self.name = name
(self.strength, self.dexterity,
self.intelligence, self.luck) = stats
Foo 10 9

In this case, though, I don't see any value in a specific sequence, so
a mapping looks better to me and gives the caller more flexibility in
how to set it up.

class Character(object):
stat_keys = ['strength', 'dexterity', 'intelligence', 'luck']
def __init__(self, name, stats):
self.name = name
self.stats = {}
for (stat_key, stat_value) in [(k, stats[k])
for k in self.stat_keys]:
setattr(self, stat_key, stat_value)
... dexterity = 11, luck = 10,
... strength = 10, intelligence = 9,
... )) Foo 10 9
I'm trying to think ahead to when I might want to add new
attributes

In which case you almost certainly want a mapping, not a sequence.
and I want to make sure this doesn't get crazy with individual
parameters instead of just the one list.

This is one of the many reasons why Python's built-in composite types
are so useful. A group of semantically-related values can be passed as
a single composite value.
 
N

Nick Craig-Wood

Larry Bates said:
John said:
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?

I'm trying to think ahead to when I might want to add new attributes,
and I want to make sure this doesn't get crazy with individual
parameters instead of just the one list.

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.

Sounds like what you should be doing is something like keyword arguments
instead.

class Character(object):
def __init__(self, name, **kwargs):
self.name=name
for key, value in kwargs.items():
setattr(self, key, value)


z=Character('name', strength=10, dexterity=5, intelligence=3,
luck=0)

I would say this is a bad idea because you'll get no error messages if
you mis-spell 'dexterity' for instance.

I'd prefer to see a bit more error checking, something like

class Character(object):
attributes = set(['strength', 'dexterity', 'intelligence', 'luck'])
def __init__(self, name, **kwargs):
self.name=name
for key, value in kwargs.items():
if key not in self.attributes:
raise AttributeError("Bad attribute %s for %s" % (key, self.__class__.__name__))
setattr(self, key, value)


z = Character('name', strength=10, dexterity=5, intelligence=3, luck=0)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "z.py", line 7, in __init__
raise AttributeError("Bad attribute %s for %s" % (key, self.__class__.__name__))
AttributeError: Bad attribute dextrity for Character
You can then add to attributes in the subclasses

class MagicCharacter(Character):
attributes = Character.attributes | set(['spells', 'wand'])

If I was writing this, I'd probably just stick to named parameters
unless I had more than 10 parameters. Named parameters are easy to
manage, and you never get confused by their position.

Also pychecker understands named parameters where as if you use a
scheme like the above you'll cause pychecker problems!
 
J

John Salerno

John said:
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?

Thanks guys! The main suggestion seems to be to use setattr(), so I
might give that a try. But I do like Steve's suggestion that it's better
to be explicit about each attribute, instead of just accepting a list of
numbers (but I can't help but feel that for some reason this is better,
because it's more general). We shall see! :)
 
B

Ben Finney

John Salerno said:
But I do like Steve's suggestion that it's better to be explicit
about each attribute, instead of just accepting a list of numbers
(but I can't help but feel that for some reason this is better,
because it's more general).

If you pass a *mapping* of the "I-might-want-to-add-more-in-the-future"
values, then you get both explicit *and* expandable, without an
arbitrary unneeded sequence.
 
J

John Salerno

Ben said:
If you pass a *mapping* of the "I-might-want-to-add-more-in-the-future"
values, then you get both explicit *and* expandable, without an
arbitrary unneeded sequence.

Do you mean by using the **kwargs parameter? If I do this, doesn't it
mean that *anything* could be added though? Misspelled words and
completely unrelated attributes as well?

Or does this matter as long as you are handling the processing yourself
internally and not allowing users access to the Character class?
 
S

Steven D'Aprano

Do you mean by using the **kwargs parameter?

Yes, that's what Ben is talking about.
If I do this, doesn't it
mean that *anything* could be added though? Misspelled words and
completely unrelated attributes as well?

**kwargs flexibility carries a risk. You may consider it worthwhile or not.

Or does this matter as long as you are handling the processing yourself
internally and not allowing users access to the Character class?

Only you can decide whether **kwargs' convenience and flexibility
outweighs its risk.
 
S

Steven D'Aprano


Well, that'll teach me to put words in your mouth.

[snip]
If you have a group of named, semantically-related, unsequenced values,
pass them into the function as a mapping object (a dict object).

Still, if you are doing this:

mapping_object = {"strength": roll_dice(10),
"intelligence":roll_dice(10),
"dexterity":roll_dice(10)}
my_character = Character(mapping_object)

then there is little benefit to building the dict just for the purposes of
passing it to Character(), never to use it again, not when you can do this:

my_character = Character(strength: roll_dice(10),
intelligence:roll_dice(10), dexterity:roll_dice(10))

If you happen to already have collected your character attributes in a
mapping object for some other reason, then well and good, pass it into the
function. Otherwise, well, I believe the correct container for
character attributes is a Character, not a dict.
 
A

Antoon Pardon


Well, that'll teach me to put words in your mouth.

[snip]
If you have a group of named, semantically-related, unsequenced values,
pass them into the function as a mapping object (a dict object).

Still, if you are doing this:

mapping_object = {"strength": roll_dice(10),
"intelligence":roll_dice(10),
"dexterity":roll_dice(10)}
my_character = Character(mapping_object)

then there is little benefit to building the dict just for the purposes of
passing it to Character(), never to use it again, not when you can do this:

my_character = Character(strength: roll_dice(10),
intelligence:roll_dice(10), dexterity:roll_dice(10))

But you will have to adapt this if you want extra or different
characteristics.

Personnally I would prefer something like:

chardict = {}
for char in characteristics:
chardict[char] = roll_dice(10)

my_character = Character(chardict)

This way you only have to keep your characteristics in one place.
If you happen to already have collected your character attributes in a
mapping object for some other reason, then well and good, pass it into the
function. Otherwise, well, I believe the correct container for
character attributes is a Character, not a dict.

What is wrong with keeping the character attributes in a dict in the
Character?
 
S

Steven D'Aprano

Ben Finney wrote:
If you pass a *mapping* of the
"I-might-want-to-add-more-in-the-future" values, then you get both
explicit *and* expandable, without an arbitrary unneeded sequence.

Do you mean by using the **kwargs parameter?

No.

Well, that'll teach me to put words in your mouth.

[snip]
If you have a group of named, semantically-related, unsequenced values,
pass them into the function as a mapping object (a dict object).

Still, if you are doing this:

mapping_object = {"strength": roll_dice(10),
"intelligence":roll_dice(10),
"dexterity":roll_dice(10)}
my_character = Character(mapping_object)

then there is little benefit to building the dict just for the purposes of
passing it to Character(), never to use it again, not when you can do this:

my_character = Character(strength: roll_dice(10),
intelligence:roll_dice(10), dexterity:roll_dice(10))

But you will have to adapt this if you want extra or different
characteristics.

Sure, but only in one place:

# now have charisma
my_character = Character(strength=roll_dice(8),
intelligence=roll_dice(12), dexterity=roll_dice(20),
charisma=roll_dice(6))

If all the char attributes are initialised with the same function, it
may make sense to set them in a loop, as you do below. But if they are all
calculated differently, as above, then you lose the benefit of a loop.
Personnally I would prefer something like:

chardict = {}
for char in characteristics:
chardict[char] = roll_dice(10)

my_character = Character(chardict)

This way you only have to keep your characteristics in one place.

As I do.

Remember, the prerequisite for my suggestion to make sense is that, once
you've created your initial character attributes and stuck them in a dict,
you never use the dict again. Also, I'm assuming the constraint that there
is a manageably small number of character attributes.

What is wrong with keeping the character attributes in a dict in the
Character?

For the same reason we typically say object.attribute rather than
object.__dict__[attribute]

Perhaps you missed the original post, where one of the constraints was
that character attributes in the game were also object attributes. E.g.

class Character(object):
def __init__(self, strength):
self.strength = strength

The question posed was, what is the best way of calling __init__ with
values for those character attributes?

If the character attributes vary at runtime, or there are many of them, or
if they are needed together (rather than individually) in multiple places
apart from Character.__init__, then it makes sense to bundle them up in a
dict and pass the dict around, like Ben and now you are suggesting.

But if the character attributes are not varying, and there are only a few,
and they only get used collectively for Character.__init__, then I don't
believe there is any advantage to putting them in a dict to be used once
and then tossed away.

To give an analogy, if you are writing a coordinate class, you would
likely do something like this:

class Coord(object):
def __init__(self, x, y):
self.x = x
self.y = y

If you had a lot of dimensions, you'd change your entire model:

class Coord(object):
def __init__(self, alist):
self.coordinates = alist

But it is unlikely that a tactic like this would be worth the extra work:


class Coord(object):
def __init__(self, mapping_object):
# check that mapping_object has the right keys
expected = ['x', 'y']
for key in expected:
if key not in mapping_object.keys(): raise KeyError
# do something with mapping_object
self.__dict__.update(mapping_object)

mapping = {'x': some_value, 'y': some_value}
point = Coord(mapping)
del mapping # never use it again after construction
 
S

Steve Holden

Steven said:

Well, that'll teach me to put words in your mouth.

[snip]
If you have a group of named, semantically-related, unsequenced values,
pass them into the function as a mapping object (a dict object).

Still, if you are doing this:

mapping_object = {"strength": roll_dice(10),
"intelligence":roll_dice(10),
"dexterity":roll_dice(10)}
my_character = Character(mapping_object)

then there is little benefit to building the dict just for the purposes of
passing it to Character(), never to use it again, not when you can do this:

my_character = Character(strength: roll_dice(10),
intelligence:roll_dice(10), dexterity:roll_dice(10))
Except, of course, that you *can't* do that: I think you meant to use
equals signs instead of colons?
If you happen to already have collected your character attributes in a
mapping object for some other reason, then well and good, pass it into the
function. Otherwise, well, I believe the correct container for
character attributes is a Character, not a dict.
regards
Steve
 
A

Antoon Pardon

On Thu, 09 Nov 2006 12:27:12 +1100, Ben Finney wrote:


Ben Finney wrote:
If you pass a *mapping* of the
"I-might-want-to-add-more-in-the-future" values, then you get both
explicit *and* expandable, without an arbitrary unneeded sequence.

Do you mean by using the **kwargs parameter?

No.

Well, that'll teach me to put words in your mouth.

[snip]
If you have a group of named, semantically-related, unsequenced values,
pass them into the function as a mapping object (a dict object).

Still, if you are doing this:

mapping_object = {"strength": roll_dice(10),
"intelligence":roll_dice(10),
"dexterity":roll_dice(10)}
my_character = Character(mapping_object)

then there is little benefit to building the dict just for the purposes of
passing it to Character(), never to use it again, not when you can do this:

my_character = Character(strength: roll_dice(10),
intelligence:roll_dice(10), dexterity:roll_dice(10))

But you will have to adapt this if you want extra or different
characteristics.

Sure, but only in one place:

Are your sure? The OP had subclasses like Fighter, Mage etc.
So it seems that you have to do something like this for
every such subclass. Or am I missing something.
# now have charisma
my_character = Character(strength=roll_dice(8),
intelligence=roll_dice(12), dexterity=roll_dice(20),
charisma=roll_dice(6))

If all the char attributes are initialised with the same function, it
may make sense to set them in a loop, as you do below. But if they are all
calculated differently, as above, then you lose the benefit of a loop.

Not necesarily. You could have a table as follows:

characteristics = [("strength", partial(roll_dice, 8)),
("inteligence" , partial(roll_dice, 12),
...

chardict={}
for char, roller in characteristics:
chardict[char] = roller()

Personnally I would prefer something like:

chardict = {}
for char in characteristics:
chardict[char] = roll_dice(10)

my_character = Character(chardict)

This way you only have to keep your characteristics in one place.

As I do.

Remember, the prerequisite for my suggestion to make sense is that, once
you've created your initial character attributes and stuck them in a dict,
you never use the dict again. Also, I'm assuming the constraint that there
is a manageably small number of character attributes.

That depends on how the rest of the game is set up. Suppose the
character is wandering through a maze. Now in the maze are "tests"
which require the character to roll under a certain characteristic
with a certain bonus/malus. So such a test object value may be
equivallent to ("strength" , -2) I think such a setup will be
easier to implement if you keep the characteristics as a dictionary
within a character instance.

Now whether such a scheme would suit the OP is up to him to decide.
What is wrong with keeping the character attributes in a dict in the
Character?

For the same reason we typically say object.attribute rather than
object.__dict__[attribute]

But we also typically use:

object.some_dict[some_characteristic]

rather than

object.__dict__[some_characterictic]

where some_charaterictic contains the name of a characteristic.
Perhaps you missed the original post, where one of the constraints was
that character attributes in the game were also object attributes. E.g.

class Character(object):
def __init__(self, strength):
self.strength = strength

The question posed was, what is the best way of calling __init__ with
values for those character attributes?

Well if that is what he wants.
If the character attributes vary at runtime, or there are many of them, or
if they are needed together (rather than individually) in multiple places
apart from Character.__init__, then it makes sense to bundle them up in a
dict and pass the dict around, like Ben and now you are suggesting.

It is not only that they can vary ay runtime. There is also the
possibility that you don't know in advance which one you will need
because that is decided by the environment. I think that would be
an argument in favor of a dict too.
But if the character attributes are not varying, and there are only a few,
and they only get used collectively for Character.__init__, then I don't
believe there is any advantage to putting them in a dict to be used once
and then tossed away.

I guess it depends on how he will need those characteristics later on.
To give an analogy, if you are writing a coordinate class, you would
likely do something like this:

class Coord(object):
def __init__(self, x, y):
self.x = x
self.y = y

But what if the application often needed one value from such an
object. So lots of time you would be given a coord plus an
indication of what value was wanted by giving a name, so that
you ended up doing a lot of getattr(coord, name) calls.
Because whether you needed the x or y coordinate is decided
by the data and not by the program structure.


May be the O.P. should decide first on how he is going to use
these characteristics in the rest of his game before deciding
what is the best approach.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,187
Latest member
RosaDemko

Latest Threads

Top