Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Python > assigning values in __init__

Reply
Thread Tools

assigning values in __init__

 
 
John Salerno
Guest
Posts: n/a
 
      11-06-2006
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.
 
Reply With Quote
 
 
 
 
Larry Bates
Guest
Posts: n/a
 
      11-06-2006
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?
>
> 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
 
Reply With Quote
 
 
 
 
Gerard Flanagan
Guest
Posts: n/a
 
      11-06-2006

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?
>


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

 
Reply With Quote
 
Steve Holden
Guest
Posts: n/a
 
      11-07-2006
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?
>
> 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
--
Steve Holden +44 150 684 7255 +1 800 494 3119
Holden Web LLC/Ltd http://www.holdenweb.com
Skype: holdenweb http://holdenweb.blogspot.com
Recent Ramblings http://del.icio.us/steve.holden

 
Reply With Quote
 
Steven D'Aprano
Guest
Posts: n/a
 
      11-07-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.

 
Reply With Quote
 
Ben Finney
Guest
Posts: n/a
 
      11-07-2006
John Salerno <(E-Mail Removed)> writes:

> 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 = Character("Foo", (10, 11, 9, 10))
>>> print foo.name, foo.strength, foo.intelligence

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)

>>> foo = Character("Foo", dict(

... dexterity = 11, luck = 10,
... strength = 10, intelligence = 9,
... ))
>>> print foo.name, foo.strength, foo.intelligence

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.

--
\ "I hope that after I die, people will say of me: 'That guy sure |
`\ owed me a lot of money.'" -- Jack Handey |
_o__) |
Ben Finney

 
Reply With Quote
 
Nick Craig-Wood
Guest
Posts: n/a
 
      11-07-2006
Larry Bates <(E-Mail Removed)> wrote:
> 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?
> >
> > 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)

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

<Character object at 0xb7dac72c>
>>> Character('name', strength=10, dextrity=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'])

>>> MagicCharacter('name', strength=10, dexterity=5, intelligence=3, luck=0, spells=1)

<MagicCharacter object at 0xb7ce86ac>
>>>


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!

--
Nick Craig-Wood <(E-Mail Removed)> -- http://www.craig-wood.com/nick
 
Reply With Quote
 
John Salerno
Guest
Posts: n/a
 
      11-07-2006
John Salerno wrote:

> 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!
 
Reply With Quote
 
Ben Finney
Guest
Posts: n/a
 
      11-07-2006
John Salerno <(E-Mail Removed)> writes:

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

--
\ "He may look like an idiot and talk like an idiot but don't let |
`\ that fool you. He really is an idiot." -- Groucho Marx |
_o__) |
Ben Finney

 
Reply With Quote
 
John Salerno
Guest
Posts: n/a
 
      11-08-2006
Ben Finney wrote:
> John Salerno <(E-Mail Removed)> writes:
>
>> 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.
>


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?
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Should one always add super().__init__() to the __init__? Ramchandra Apte Python 17 09-30-2012 12:04 PM
__init__ with multiple list values Gnarlodious Python 4 11-02-2011 11:04 AM
Assigning methods to objects, and assigning onreadystatechange to an XMLHttpRequest -- an inconsistency? weston Javascript 1 09-22-2006 09:33 AM
super(...).__init__() vs Base.__init__(self) Kent Johnson Python 7 02-12-2006 08:59 PM
__new__ does not call __init__ as described in descrintro.html (WAS:Can __new__ prevent __init__ from being called?) Steven Bethard Python 2 02-16-2005 06:50 AM



Advertisments