13

Is there\How would you build an equivalent of python's very useful collections.defaultdict?

Imagined usage of such a container:

>>> a = collections.defaultlist(0)
>>> a[2]=7
>>> a[4]='x'
>>> a
[0,0,7,0,'x']

UPDATE: I've added a follow up question to add even more functionality to this construct

Community
  • 1
  • 1
Jonathan Livni
  • 101,334
  • 104
  • 266
  • 359
  • @Josh Lee: Did you read the tags at all? No JavaScript at all-- this is a Python question. – Platinum Azure Jan 03 '12 at 22:31
  • Why would you want that? – Cat Plus Plus Jan 03 '12 at 22:32
  • @PlatinumAzure Did you see the smiley at all? – Josh Lee Jan 03 '12 at 22:33
  • @CatPlusPlus - Half because I need it for something specific, half out of curiosity :) – Jonathan Livni Jan 03 '12 at 22:34
  • @Jonathan: You more than likely do *not* need it. – Cat Plus Plus Jan 03 '12 at 22:34
  • 1
    @CatPlusPlus - Due to a series of annoying constraints, which I should definitely remove by a future refactoring effort, I _currently_ need _exactly_ this – Jonathan Livni Jan 03 '12 at 22:40
  • 5
    And what is the expected behaviour for `a[-1] = 'x'` ? – wim Jan 03 '12 at 22:53
  • Please feel free to explain these "annoying constraints". – Karl Knechtel Jan 03 '12 at 23:47
  • 1
    constraints which should be refactored out (I know guys, please don't give me a rough time about it): This object travels around the system where many modules (consumers) depend on it having at least a tuple behavior, giving semantic meaning to items' index and to the list's length. Producers of this object don't know in advance its length and iterate over various sources while building it. Basically I'm trying to encapsulate a reoccuring list building logic which I could rewrite several times, but why? especially after I've discovered the incredible ease of using `defaultdict`... – Jonathan Livni Jan 04 '12 at 13:36
  • @wim - I think Finn's answer gives an intuitive approach to assignment to `-1` index by keeping it simple - simple access index `-1` without any special behavior – Jonathan Livni Jan 04 '12 at 13:46

5 Answers5

12

I think this would be a bit confusing to use; however, here's my first thought on how to do it:

class defaultlist(list):
    def __init__(self, fx):
        self._fx = fx

    def __setitem__(self, index, value):
        while len(self) <= index:
            self.append(self._fx())
        list.__setitem__(self, index, value)

This takes a callable (I think that's how defaultdict works) for the default value.

When I run:

a = defaultlist(int)
print a
a[2] = 7
a[4] = 'x'
print a

I get back:

[]
[0, 0, 7, 0, 'x']
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
Finn
  • 1,823
  • 2
  • 15
  • 31
4

If all you need is indexed access and not slicing / append, etc, then just use a defaultdict.

(if you really want perl / js semantics on this, you could subclass list __get__ and __set__)

Gregg Lind
  • 20,690
  • 15
  • 67
  • 81
2

A slightly enhanced version from answer by @Finn.

class defaultlist(list):
    """List returning default value when accessing uninitialized index.

    Original implementation: http://stackoverflow.com/a/8719940/315168
    """

    def __init__(self, fx):
        self._fx = fx

    def __setitem__(self, index, value):
        while len(self) <= index:
            self.append(self._fx())
        list.__setitem__(self, index, value)

    def __getitem__(self, index):
        """Allows self.dlist[0] style access before value is initialized."""
        while len(self) <= index:
            self.append(self._fx())
        return list.__getitem__(self, index)
Mikko Ohtamaa
  • 82,057
  • 50
  • 264
  • 435
2

My proposal:

def xtend(f):
    def wrap(self, index, *args):
        if len(self) <= index:
            self.extend([self._gen()] * (index - len(self) + 1))
        return f(self, index, *args)
    return wrap

class defaultlist(list):
    def __init__(self, gen, lst = []):
        list.__init__(self, lst)
        self._gen = gen

    __setitem__ = xtend(list.__setitem__)
    __getitem__ = xtend(list.__getitem__)

Results:

>>> a = defaultlist(int, [1, 2, 3])
>>> a[10] = 'x'
>>> a[2] = 7
>>> print a
[1, 2, 7, 0, 0, 0, 0, 0, 0, 0, 'x']
Ricardo Cárdenes
  • 9,004
  • 1
  • 21
  • 34
0

Perhaps the easiest way is using a dict:

>>> a = {}
>>> a[2] = 7
>>> a[4] = 'x'
>>> [a[i] if i in a else 0 for i in xrange(max(a) + 1)]
[0, 0, 7, 0, 'x']
fransua
  • 1,559
  • 13
  • 30