Parameter lists
Bruno Desthuilliers
bdesth.quelquechose at free.quelquepart.fr
Mon Feb 5 18:01:12 EST 2007
Steven D'Aprano a écrit :
> 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.
>
>
(snip)
>
> 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 ===
>
(snip)
>
> === Disadvantages ===
>
(snip)
> (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", ...)
>
How to overcome these two problem - just overcomplexifying things a bit:
class StatsAttribute(object):
def __init__(self, default=None):
self._default = default
self._attrname = None # set by the StatType metaclass
def __get__(self, instance, cls):
if instance is None:
return self
return instance._stats.get(self._attrname, self._default)
def __set__(self, instance, value):
instance._stats[self._attrname] = value
class StatsType(type):
def __init__(cls, name, bases, attribs):
super(StatsType, cls).__init__(name, bases, attribs)
statskeys = getattr(cls, '_statskeys', set())
for name, attrib in attribs.items():
if isinstance(attrib, StatsAttribute):
# sets the name to be used to get/set
# values in the instance's _stats dict.
attrib._attrname = name
# and store it so we know this is
# an expected attribute name
statskeys.add(name)
cls._statskeys = statskeys
class Stats(object):
__metaclass__ = StatsType
def __init__(self, **stats):
self._stats = dict()
for name, value in stats.items():
if name not in self._statskeys:
# fixes disadvantage #2 : we won't have unexpected kw args
msg = "%s() got an unexpected keyword argument '%s'" \
% (self.__class__.__name__, name)
raise TypeError(msg)
setattr(self, name, value)
# just a dummy object, I didn't like the
# idea of using strings litterals for things
# like armors or weapons... And after all,
# it's supposed to be overcomplexified, isn't it ?
class _dummy(object):
def __init__(self, **kw):
self._kw = kw
def __getattr__(self, name):
return self._kw[name]
class Leather(_dummy): pass
class Sword(_dummy): pass
class FullPlate(_dummy): pass
class MagicTwoHanded(_dummy): pass
# let's go:
class Warrior(Stats):
# fixes disatvantage 3 : we won't have name clash
strength = StatsAttribute(default=12)
armour = StatsAttribute(default=Leather())
weapon = StatsAttribute(default=Sword())
bigBill = Warrior(
strength=120,
armour=FullPlate(),
weapon=MagicTwoHanded(bonus=20)
)
try:
wontDo = Warrior(
sex_appeal = None
)
except Exception, e:
print "got : %s" % e
Did I won a MasterProgrammer (or at least a SeasonnedPro) award ?-)
http://sunsite.nus.sg/pub/humour/prog-evolve.html
Err... me go to bed now...
> 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)
And then allow for Python source code in the INI file (that will be used
to create methods) to specify behaviour ?-)
Ok, this time I really go to bed !-)
More information about the Python-list
mailing list