instance variable weirdness

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

    #1

    instance variable weirdness

    Hello,

    I have written the following script to illustrate a problem in my code:

    class BaseClass(objec t):
    def __init__(self):
    self.collection = []

    class MyClass(BaseCla ss):
    def __init__(self, name, collection=[]):
    BaseClass.__ini t__(self)
    self.name = name
    self.collection = collection

    if __name__ == '__main__':
    seq = []
    inst = None
    for i in xrange(5):
    inst = MyClass(str(i))
    inst.collection .append(i)
    seq.append(inst )
    inst = None
    for i in xrange(5):
    inst = MyClass(str(i)+ '~', [])
    inst.collection .append(i)
    seq.append(inst )
    inst = None
    for i in seq:
    print "Instance '%s'; collection = %s" % (i.name,
    str(i.collectio n))

    The output I get is:[color=blue][color=green][color=darkred]
    >>>[/color][/color][/color]
    Instance '0'; collection = [0, 1, 2, 3, 4]
    Instance '1'; collection = [0, 1, 2, 3, 4]
    Instance '2'; collection = [0, 1, 2, 3, 4]
    Instance '3'; collection = [0, 1, 2, 3, 4]
    Instance '4'; collection = [0, 1, 2, 3, 4]
    Instance '0~'; collection = [0]
    Instance '1~'; collection = [1]
    Instance '2~'; collection = [2]
    Instance '3~'; collection = [3]
    Instance '4~'; collection = [4][color=blue][color=green][color=darkred]
    >>>[/color][/color][/color]

    I don't understand why the first loop doesn't give the same result as
    the second loop. Can somebody enlighten me?

    Wietse

  • Felipe Almeida Lessa

    #2
    Re: instance variable weirdness

    Em Sex, 2006-04-14 às 09:18 -0700, wietse escreveu:[color=blue]
    > def __init__(self, name, collection=[]):[/color]

    Never, ever, use the default as a list.
    [color=blue]
    > self.collection = collection[/color]

    This will just make a reference of self.collection to the collection
    argument.
    [color=blue]
    > inst.collection .append(i)[/color]

    As list operations are done in place, you don't override the
    self.collection variable, and all instances end up by having the same
    list object.

    To solve your problem, change
    def __init__(self, name, collection=[]):
    BaseClass.__ini t__(self)
    self.name = name
    self.collection = collection # Will reuse the list
    to
    def __init__(self, name, collection=None ):
    BaseClass.__ini t__(self)
    self.name = name
    if collection is None:
    collection = [] # Will create a new list on every instance
    self.collection = collection


    --
    Felipe.

    Comment

    • Felipe Almeida Lessa

      #3
      Re: instance variable weirdness

      Em Sex, 2006-04-14 às 13:30 -0300, Felipe Almeida Lessa escreveu:[color=blue]
      > To solve your problem, change
      > def __init__(self, name, collection=[]):
      > BaseClass.__ini t__(self)
      > self.name = name
      > self.collection = collection # Will reuse the list
      > to
      > def __init__(self, name, collection=None ):
      > BaseClass.__ini t__(self)
      > self.name = name
      > if collection is None:
      > collection = [] # Will create a new list on every instance
      > self.collection = collection[/color]

      Or if None is valid in your context, do:

      __marker = object()
      def __init__(self, name, collection=__ma rker):
      BaseClass.__ini t__(self)
      self.name = name
      if collection is __marker:
      collection = [] # Will create a new list on every instance
      self.collection = collection

      --
      Felipe.

      Comment

      • Steven D'Aprano

        #4
        Re: instance variable weirdness

        On Fri, 14 Apr 2006 13:30:49 -0300, Felipe Almeida Lessa wrote:
        [color=blue]
        > Em Sex, 2006-04-14 às 09:18 -0700, wietse escreveu:[color=green]
        >> def __init__(self, name, collection=[]):[/color]
        >
        > Never, ever, use the default as a list.[/color]

        Unless you want to use the default as a list.

        Sometimes you want the default to mutate each time it is used, for example
        that is a good technique for caching a result:

        def fact(n, _cache=[1, 1, 2]):
        "Iterative factorial with a cache."
        try:
        return _cache[n]
        except IndexError:
        start = len(_cache)
        product = _cache[-1]
        for i in range(start, n+1):
        product *= i
        _cache.append(p roduct)
        return product


        --
        Steven.

        Comment

        • Felipe Almeida Lessa

          #5
          Re: instance variable weirdness

          Em Sáb, 2006-04-15 às 04:03 +1000, Steven D'Aprano escreveu:[color=blue]
          > Sometimes you want the default to mutate each time it is used, for example
          > that is a good technique for caching a result:
          >
          > def fact(n, _cache=[1, 1, 2]):
          > "Iterative factorial with a cache."
          > try:
          > return _cache[n]
          > except IndexError:
          > start = len(_cache)
          > product = _cache[-1]
          > for i in range(start, n+1):
          > product *= i
          > _cache.append(p roduct)
          > return product[/color]

          I prefer using something like this for the general case:

          def cached(function ):
          """Decorato r that caches the function result.

          There's only one caveat: it doesn't work for keyword arguments.
          """
          cache = {}
          def cached_function (*args):
          """This is going to be replaced below."""
          try:
          return cache[args]
          except KeyError:
          cache[args] = function(*args)
          return cache[args]
          cached_function .__doc__ = function.__doc_ _
          cached_function .__name__ = function.__name __
          return cached_function



          And for this special case, something like:

          def fact(n):
          "Iterative factorial with a cache."
          cache = fact.cache
          try:
          return cache[n]
          except IndexError:
          start = len(cache)
          product = cache[-1]
          for i in range(start, n+1):
          product *= i
          cache.append(pr oduct)
          return product
          fact.cache = [1, 1, 2]



          This may be ugly, but it's less error prone. Also, we don't expose the
          cache in the function's argument list.

          --
          Felipe.

          Comment

          Working...