Reputation: 42421
I'm working on a project where I need a method that takes an arbitrary Python object, and if that object acts like a dict
, list
, or tuple
-- meaning that it supports the idea of accessing members of a collection via a key or index -- my method should return an iterator that can traverse the object's key-value or index-value pairs. It would also be fine for my purposes if the iterator simply traversed the objects keys or indexes. Here's the code I've got so far:
from collections import Mapping, Sequence
# Tuple used to identify string-like objects in Python 2 or 3.
STRINGS = (str, unicode) if str is bytes else (str, bytes)
def get_keyval_iter(obj):
if isinstance(obj, STRINGS): return None
elif isinstance(obj, Sequence): return enumerate(obj)
elif isinstance(obj, Mapping): return getattr(obj, 'iteritems', obj.items)()
else: return None
# For example:
print list(get_keyval_iter([0, 11, 22])) # [(0, 0), (1, 11), (2, 22)]
print list(get_keyval_iter(dict(a = 1, b = 2))) # [('a', 1), ('b', 2)]
print [ get_keyval_iter("foobar") ] # [None]
print [ get_keyval_iter(1234) ] # [None]
I don't like this solution for two reasons: (1) on general principle, I'd rather query the object's interface than check its type; (2) my code will return None
for user-defined classes whose objects fail the isinstance
tests but that nonetheless support the __getitem__
protocol and could, in theory, give me an iterator over the relevant keys or indexes.
Here's the code I'd like to write: return obj.__getitemiter__()
-- or something like that.
Am I overlooking an obvious way to get what I need -- namely, an iterator over an arbitrary object's keys or indexes (or over its key-value or index-value pairs)?
Upvotes: 0
Views: 622
Reputation: 1123360
You want to use the ABCs defined in the collections
module only to detect a mapping (because you want to iterate over key-value pairs instead of keys), and use the standard iter()
function for everything else:
import collections
def get_keyval_iter(obj):
if isinstance(obj, collections.Mapping):
return obj.iteritems()
try:
return enumerate(iter(obj))
except TypeError:
# not iterable
return None
Note the iter()
call; it takes any iterable sequence object and returns an iterator object that will operate on it. It supports both objects that implement the iterator protocol and objects that support a .__getitem__()
method:
[...] o must be a collection object which supports the iteration protocol (the
__iter__()
method), or it must support the sequence protocol (the__getitem__()
method with integer arguments starting at0
).
So where collections.Sequence
looks for both a __getitem__
and a __len__
method, iter()
only looks for __getitem__
.
Note that you should not go overboard with accepting and handling too many different types; there should not be an exception for strings here, for example. Rethink your code to perhaps be more strict in what you promise to handle.
Upvotes: 1