11

I need to test if objects read in from a file (and evaled) with ConfigParser are mappings.

Not entirely sure about the terminology here, but let me explain. Given that my object is called O it must support being used in the following manner:

def tester(**kwargs):
     print kwargs

tester(**O)

If O was not supporting the ** this would result in a TypeError, e.g. TypeError: test() argument after ** must be a mapping, not tuple.

It's a really simple scenario, but I need to know that O will work before using it and I need to be absolutely certain it won't fail. If I was testing O for being an iterable, I would use something like:

try:
   iter(O)
except:
   O = tuple()

As discussed in In Python, how do I determine if an object is iterable?

I can't find any parallel for mappings. As discussed in the same answer above, using isinstance and collections isn't an good solution.

So do I have to make my tester-function above (without the print) as my own mapping-test when loading the objects like

try:
    tester(**O)
except TypeError:
    O = {}

or does python have a built in way to test this like there is for iterables? It seems there should be one.

Edit

Actually the linked answer above never spoke against the isinstance method, should have read it better...

Community
  • 1
  • 1
deinonychusaur
  • 7,094
  • 3
  • 30
  • 44
  • See also [python-custom-mapping-class-unpacking-and-keys-attribute](https://stackoverflow.com/q/33547061/7038689) - interesting discussion about how the `**kwarg`-style unpacking requires a method explicitly named `keys` that returns an iterable list, along with `__getitem__`. – Matt P Feb 27 '19 at 17:52

2 Answers2

22

Use the collections.abc.Mapping ABC:

from collections.abc import Mapping

if isinstance(O, Mapping):
    # O is a mapping

This supports any object that implements the right methods to be considered a mapping, including dict.

Demo:

>>> from collections.abc import Mapping
>>> isinstance({}, Mapping)
True
>>> isinstance((), Mapping)
False
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • But it must be derived from Mapping or something being a mapping, is this truely the same thing as supporting the ** ? – deinonychusaur Feb 19 '14 at 15:02
  • 4
    @deinonychusaur: No, `Mapping` is a ABC, an abstract base class, and any object supporting the *methods* is considered an instance. It doesn't actually need to inherit from `Mapping`, implementing the functionality is enough. – Martijn Pieters Feb 19 '14 at 15:03
  • OK thanks for the quick reply, I was only confused it seems, but python mapping-concept seems not so present here on SO hopefully it will help someone else too. – deinonychusaur Feb 19 '14 at 15:06
7

Some objects may not be instances of collections.Mapping and yet can be unpacked using double-star syntax(**):

import collections
def tester(**kwargs):
     print kwargs

class D:
    "http://stackoverflow.com/a/8601389/190597 (Raymond Hettinger)"
    def keys(self):
        return ['a', 'b']
    def __getitem__(self, key):
        return key.upper()

obj = D()
tester(**obj)
# {'a': 'A', 'b': 'B'}

print(isinstance(obj, collections.Mapping))
# False

The minimal interface that obj must supply is keys and __getitem__:

print(all(hasattr(obj, attr) for attr in ('keys', '__getitem__')))
# True
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • So you are saying is that the only true way of knowing is of testing, or potentially using `dir` (though that would be lower degree of confidence)? – deinonychusaur Feb 23 '14 at 18:50
  • 3
    No, I'm saying when `all(hasattr(obj, attr) for attr in ('keys', '__getitem__'))` is `True`, then `tester(**obj)` works. – unutbu Feb 23 '14 at 19:04
  • 1
    @unutbu More precisely, the criteria is `hasattr(obj, 'keys') and hasattr(type(obj), '__getitem__')`. The `keys` can be either a method on the object's class or a just a callable attribute on the object, but the `__getitem__` magic method *must* be a method on the object's class, since `obj[key]` looks for it on the class, not on the object. If you test for `__getitem__` on the object itself, you could get a false positive from the test and an `TypeError: ... object is not subscriptable` from `**obj`. – mtraceur Oct 26 '22 at 21:04