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 !-) -- http://mail.python.org/mailman/listinfo/python-list