views:

47

answers:

2

(I am using python 2.7) The python documentation indicates that you can pass a mapping to the dict builtin and it will copy that mapping into the new dict:

http://docs.python.org/library/stdtypes.html#mapping-types-dict

I have a class that implements the Mapping ABC, but it fails:

import collections
class Mapping(object):
    def __init__(self, dict={}): self.dict=dict
    def __iter__(self): return iter(self.dict)
    def __iter__(self): return iter(self.dict)
    def __len__(self): return len(self.dict)
    def __contains__(self, value): return value in self.dict
    def __getitem__(self, name): return self.dict[name]

m=Mapping({5:5})
dict(m)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: cannot convert dictionary update sequence element #0 to a sequence
collections.Mapping.register(Mapping)
dict(m)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: cannot convert dictionary update sequence element #0 to a sequence

However, if my class subclasses collections.Mapping then it works fine:

import collections
class Mapping(collections.Mapping):
    def __init__(self, dict={}): self.dict=dict
    def __iter__(self): return iter(self.dict)
    def __iter__(self): return iter(self.dict)
    def __len__(self): return len(self.dict)
    def __contains__(self, value): return value in self.dict
    def __getitem__(self, name): return self.dict[name]

m=Mapping({5:5})
dict(m)
# {5: 5}

I thought the whole point of the ABCs was to allow registration to work the same as subclassing (for isinstance and issubclass anyway). So what's up here?

+2  A: 

Registration does not give you the "missing methods" implemented on top of those you define: in fact, registration is non-invasive with respect to the type you're registering -- nothing gets added to it, nothing gets removed, nothing gets altered. It only affects isinstance and issubclass checks: nothing more, nothing less.

Subclassing an ABC can and does give you plenty of methods implemented "for free" by the ABC on top of those you're having to define yourself.

The semantics of an operation that's totally non-invasive like registering, compared to those of an operation that's intended to enrich a class, like subclassing, obviously cannot be identical; so your understanding of "the whole point of the ABCs" is imperfect -- ABCs have two points, one obtained by subclassing ("invasive"), one by registering (non-invasive).

Note that you can always multiply-inherit if you already have a class like your original Mapping: class GoogMapping(Mapping, collections.Mapping): ... will give you the same results as inheriting Mapping directly from collections.Mapping -- a new type with all the auxiliary methods added by collections.Mapping.

Alex Martelli
Thanks! That was informative. I didn't want to subclass because I don't want the particular "for free" methods you get when you subclass. I thought that I had covered my class enough that I could cast it to a dict, but I was wrong. Thanks again.
Eric Snow
Alex Martelli
As far as the Mapping class goes, does that mean a proper Mapping must have all the methods of Mapping, even the for-free ones? I would think yes, since keys was expected for dict. Or is it that dict could have used iter to get the keys?
Eric Snow
By the way, thanks for pointing to the code. I wasn't sure where to look to find out what dict was looking for to know if something is a Mapping. Now I do. :)
Eric Snow
@Eric, using `__iter__` would not let dict distinguish between a sequence of pairs and a mapping. Modern code might test for (the C eqv of) `isinstance` w/collections.Mapping, but then might also get astonished if the alleged instance has not in fact implemented all methods in `collections.Mapping` (you **are** after all supposed to implement EVERY method of an ABC which somebody might want to call before you register into it -- otherwise, as they say, "on your own head be it";-). Old code like `dict` checks directly for presence of the methods it will then use.
Alex Martelli
Got it. So for any mapping you must also implement the Mixin Methods listed on the `collections` page and not just the Abstract Methods.
Eric Snow
A: 

Ah, looks like dict() is looking for the keys method... It doesn't use the ABCs.

Eric Snow
It does (`PyMapping_Keys` specifically) but if and only if the `keys` method is present, see my comment above (in my A's comments thread) about more details.
Alex Martelli