views:

266

answers:

2

Hi,

In Python, you can use a dictionary as the first argument to dict.fromkeys(), e.g.:

In [1]: d = {'a': 1, 'b': 2}

In [2]: dict.fromkeys(d)
Out[2]: {'a': None, 'b': None}

I tried to do the same with a dict-like object, but that always raises a KeyError, e.g.:

In [1]: class SemiDict:
   ...:     def __init__(self):
   ...:         self.d = {}
   ...:
   ...:     def __getitem__(self, key):
   ...:         return self.d[key]
   ...:
   ...:     def __setitem__(self, key, value):
   ...:         self.d[key] = value
   ...:
   ...:

In [2]: sd = SemiDict()

In [3]: sd['a'] = 1

In [4]: dict.fromkeys(sd)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)

C:\bin\Console2\<ipython console> in <module>()

C:\bin\Console2\<ipython console> in __getitem__(self, key)

KeyError: 0

What exactly is happening here? And can it be resolved, other than using something like dict.fromkeys(sd.d)?

+1  A: 

instance of SemiDict is not a sequence. I'd imagine the most obvious solution would be to inherit from dict, why don't you do it?

SilentGhost
inheriting from `dict` is not very useful, since if you define special behaviour on `__getitem__` , using the methods `.get()` , `.pop()` , `.popitem()` , `items()` , and others, will **bypass** your custom `__getitem__` behaviour, so you end up rewriting all methods anyway.
nosklo
while this might just an example, OP doesn't exactly seem to overwrite default `__getitem__`
SilentGhost
and you **should** rewrite all methods according to docs: *It is also recommended that mappings provide the methods `keys()`, `values()`, `items()`, `get()`, `clear()`, `setdefault()`, `pop()`, `popitem()`, `copy()`, and `update()` behaving similar to those for Python’s standard dictionary objects.* http://docs.python.org/reference/datamodel.html#emulating-container-types
SilentGhost
Yeah, my point is, just inherit from `UserDict` instead of `dict` so you don't have to repeat yourself.
nosklo
You're right, this would work for the simple example I used. Since the 'real' class is somewhat more complicated, I'll go with the `__iter__` solution. Thanks!
RafG
+6  A: 

To create the dict, fromkeys iterates over its argument. So it must be an iterator. One way to make it work is to add an __iter__ method to your dict-like:

def __iter__(self):
    return iter(self.d)
nosklo
it should be added, that in the absence of `__iter__`, it will access it like a sequence, calling `__getitem__(0)`, then 1, and so on. Thereby the KeyError for 0.
kaizer.se
The default behaviour for __iter__ is to return item[0] item[1] item[2]... which is what is causing the exception you are seeing
gnibbler
@gnibbler - Pretty sure the default behavior for `iter` is to return an iterator, which will be accessed with calls to its `next()` method until it throws a `StopIteration` exception.
Triptych
Works a treat, plus I actually understand the exception now. Many thanks!
RafG
gnibbler is correct in that iter() on this class returns some kind of iterator object. This object in turn calls `__getitem__` as I said. Without a defined length, the iterator would be infinite.
kaizer.se