Parameter lists

Steven D'Aprano steve at REMOVEME.cybersource.com.au
Sun Feb 4 22:44:46 EST 2007


On Sun, 04 Feb 2007 17:45:04 +0100, Mizipzor wrote:

> Consider the following snippet of code:
> 
> ==========================
> 
> class Stats:
>     def __init__(self, speed, maxHp, armor, strength, attackSpeed, imagePath):
>         self.speed = speed
>         self.maxHp = maxHp
>         self.armor = armor
>         self.strength = strength
>         self.attackSpeed = attackSpeed
>         self.originalImage = loadTexture(imagePath)
> 
> ==========================
> 
> I little container for holding the stats for some rpg character or
> something. Now, I dont like the looks of that code, there are many
> function parameters to be sent in and if I were to add an attribute, i
> would need to add it in three places. Add it to the function
> parameters, add it to the class and assign it.
> 
> Is there a smoother way to do this? There usually is in python, hehe.

There is no "right way" to handle the issue of initialising attributes.
The above way is very common, easy, self-documenting and doesn't have that
many disadvantages unless you have lots of parameters to deal with.


> I recall when reading python tutorials that you could do something
> like this:
> 
> foo(*list_of_parameters):
> 
> To send many parameters as a list or a tuple. Then I could assign them
> like this:
> 
> class Stats:
>     def __init__(self, *li):
>         self.speed = li[0]
>         self.maxHp = li[1]
>         (...)


That's even worse.

Which is correct?

Stats(..., armour, stealth, ...)
Stats(..., stealth, armour, ...)

You have to read the code to find out. Not just the function definition,
but you actually have to read through all the assignments. Bad bad bad.


> Or maybe there is an even niftier way that lets me iterate through
> them? Hmm... but that may lead to that I need to store them in a way
> that makes it cumbersome to access them later.


def __init__(self, **kwargs):
    for key in kwargs:
        if hasattr(self, key):
            # key clashes with an existing method or attribute
            raise ValueError("Attribute clash for '%s'" % key)
    self.__dict__.update(kwargs)


=== Advantages ===

(1) you can create new attributes without changing any code;
(2) creating an instance is self-documenting:

Stats(armour="chainmail", stealth=2, strength=4, ...)

(3) attributes can be added in any order;
(4) easy to modify the class so it inherits sensible defaults:

class Stats:
    armour = "leather"
    wisdom = 10
    dexterity = 10
    weapon = "sword"
    def __init__(self, **kwargs):
        for key in kwargs:
            if self.__dict__.has_key(key):
                raise ValueError("Attribute clash for '%s'" % key
        self.__dict__.update(kwargs)



=== Disadvantages ===

(1) You have to put in the attribute name, always:

Stats(armour="chainmail", stealth=2, strength=4, ...) instead of
Stats("chainmail", 2, 4, ...)

(2) Typos can cause strange bugs which are hard to find:

Stats(armour="chainmail", stealth=2, stregnth=4, ...)

Now your character is unexpectedly strong because it inherits the default,
and you don't know why.

(3) Easy to break your class functionality:

Stats(name_that_clashes_with_a_method="something else", ...)



If you've got lots of attributes, you're better off moving them to
something like a INI file and reading from that:

class Stats:
    defaults = "C:/path/defaults.ini"
    def __init__(self, filename=None, **kwargs):
        if not filename:
            filename = self.__class__.defaults
        self.get_defaults(filename) # an exercise for the reader
        for key in kwargs:
            if not self.__dict__.has_key(key):
                raise ValueError("Unknown attribute '%s' given" % key)
        self.__dict__.update(kwargs)

Notice that here I've changed from testing for attributes which clash to
testing for attributes which *don't* match a key in the INI file. Which is
the "best" behaviour, I leave up to you to decide.



-- 
Steven D'Aprano 






More information about the Python-list mailing list