Parameter lists

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Mizipzor

    #1

    Parameter lists

    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.attackSpee d = attackSpeed
    self.originalIm age = loadTexture(ima gePath)

    =============== ===========

    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.
    I recall when reading python tutorials that you could do something
    like this:

    foo(*list_of_pa rameters):

    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]
    (...)

    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.

    Any comments and/or suggestions are welcome! :)
  • bearophileHUGS@lycos.com

    #2
    Re: Parameter lists

    Mizipzor
    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?
    You may use something like this:

    def selfassign(self , locals):
    # Code from web.py http://webpy.org modified.
    for key, value in locals.iteritem s():
    if key != 'self':
    setattr(self, key, value)

    Generally used in __init__ methods, as:

    def __init__(self, foo, bar, baz=1):
    selfassign(self , locals())

    You may use it as (untested):

    class Stats:
    def __init__(self, speed, maxHp, armor, strength, attackSpeed,
    imagePath):
    selfassign(self , locals())
    self.originalIm age = loadTexture(ima gePath)
    del self.imagePath

    I don't like that del

    Bye,
    bearophile

    Comment

    • rzed

      #3
      Re: Parameter lists

      Mizipzor <mizipzor@gmail .comwrote in
      news:mailman.35 33.1170607508.3 2031.python-list@python.org :
      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.attackSpee d = attackSpeed
      self.originalIm age = loadTexture(ima gePath)
      >
      =============== ===========
      >
      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. I recall when reading python tutorials that you could do
      something like this:
      >
      foo(*list_of_pa rameters):
      >
      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]
      (...)
      >
      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.
      >
      Any comments and/or suggestions are welcome! :)
      >
      I often use something like this, based on a Martellibot posting:
      >>class Stats(dict):
      .... def __init__(self, *args, **kwds):
      .... self.update(*ar gs)
      .... self.update(kwd s)
      .... def __setitem__(sel f, key, value):
      .... return super(Stats, self).__setitem __(key, value)
      .... def __getitem__(sel f, name):
      .... try:
      .... return super(Stats, self).__getitem __(name)
      .... except KeyError:
      .... return None
      .... __getattr__ = __getitem__
      .... __setattr__ = __setitem__
      ....
      >>m = dict(a=1,b=22,c =(1,2,3))
      >>p = Stats(m,x=4,y=[5,9,11])
      >>p.y
      [5, 9, 11]
      >>p['y']
      [5, 9, 11]

      --
      rzed

      Comment

      • Jeffrey Froman

        #4
        Re: Parameter lists

        Mizipzor wrote:
        class Stats:
        def __init__(self, *li):
        self.speed = li[0]
        self.maxHp = li[1]
        (...)
        >
        Or maybe there is an even niftier way that lets me iterate through
        them?
        Using keyword arguments instead of positional parameters makes this easy:
        >>class Stats:
        .... def __init__(self, **kw):
        .... self.__dict__.u pdate(kw)
        ....
        >>stats = Stats(speed=10, maxHp=100, armor='plate mail')
        >>stats.speed
        10
        >>stats.maxHp
        100
        >>stats.armor
        'plate mail'


        Jeffrey

        Comment

        • Steven D'Aprano

          #5
          Re: Parameter lists

          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.attackSpee d = attackSpeed
          self.originalIm age = loadTexture(ima gePath)
          >
          =============== ===========
          >
          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_pa rameters):
          >
          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("Att ribute clash for '%s'" % key)
          self.__dict__.u pdate(kwargs)


          === Advantages ===

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

          Stats(armour="c hainmail", 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__.h as_key(key):
          raise ValueError("Att ribute clash for '%s'" % key
          self.__dict__.u pdate(kwargs)



          === Disadvantages ===

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

          Stats(armour="c hainmail", stealth=2, strength=4, ...) instead of
          Stats("chainmai l", 2, 4, ...)

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

          Stats(armour="c hainmail", 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="someth ing 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_defaul ts(filename) # an exercise for the reader
          for key in kwargs:
          if not self.__dict__.h as_key(key):
          raise ValueError("Unk nown attribute '%s' given" % key)
          self.__dict__.u pdate(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



          Comment

          • Bruno Desthuilliers

            #6
            Re: Parameter lists

            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.attackSpee d = attackSpeed
            > self.originalIm age = loadTexture(ima gePath)
            >>
            >>
            >>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("Att ribute clash for '%s'" % key)
            self.__dict__.u pdate(kwargs)
            >
            >
            === Advantages ===
            >
            (snip)
            >
            === Disadvantages ===
            >
            (snip)
            (2) Typos can cause strange bugs which are hard to find:
            >
            Stats(armour="c hainmail", 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="someth ing else", ...)
            >
            How to overcome these two problem - just overcomplexifyi ng 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._attr name, 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__(n ame, bases, attribs)
            statskeys = getattr(cls, '_statskeys', set())
            for name, attrib in attribs.items() :
            if isinstance(attr ib, StatsAttribute) :
            # sets the name to be used to get/set
            # values in the instance's _stats dict.
            attrib._attrnam e = name
            # and store it so we know this is
            # an expected attribute name
            statskeys.add(n ame)
            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 overcomplexifie d, isn't it ?
            class _dummy(object):
            def __init__(self, **kw):
            self._kw = kw

            def __getattr__(sel f, name):
            return self._kw[name]

            class Leather(_dummy) : pass
            class Sword(_dummy): pass
            class FullPlate(_dumm y): 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=FullPlat e(),
            weapon=MagicTwo Handed(bonus=20 )
            )

            try:
            wontDo = Warrior(
            sex_appeal = None
            )
            except Exception, e:
            print "got : %s" % e


            Did I won a MasterProgramme r (or at least a SeasonnedPro) award ?-)


            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_defaul ts(filename) # an exercise for the reader
            for key in kwargs:
            if not self.__dict__.h as_key(key):
            raise ValueError("Unk nown attribute '%s' given" % key)
            self.__dict__.u pdate(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 !-)

            Comment

            Working...