5
\$\begingroup\$

I've got a collection of users with two types of IDs (a_id, b_id) where one is a positive integer and the other is a six-letter string. For my collection class, I'd like to be able to look up users using either type of ID.

Is there something wrong with using __contains__ and __getitem__ for either type of ID?

class UserList:
    def __init__(self, users_json):
        self.a_ids = {}
        self.b_ids = {}
        for user in users_json:
            a_id = user['a_id']
            b_id = user['b_id']
            self.a_ids[a_id] = user
            self.b_ids[b_id] = user

    def __contains__(self, some_id):
        return some_id in self.a_ids or some_id in self.b_ids

    def __getitem__(self, some_id):
        try:
            return self.a_ids[some_id]
        except KeyError:
            return self.b_ids[some_id]

Update: This is for Python 3.x, and there is no implementation of __setitem__; updating users is handled in separate API functions.

\$\endgroup\$
  • \$\begingroup\$ Would you be willing to provide more code? Do you have a __setitem__? Are you using Python 3.3+? The answer to all of these could make a drastically different answer. \$\endgroup\$ – Peilonrayz Oct 24 '17 at 9:49
  • \$\begingroup\$ @Peilonrayz: Thanks for your input. I've clarified on those two questions: No, and yes. \$\endgroup\$ – Simon Shine Oct 24 '17 at 13:35
5
\$\begingroup\$
  • From Python 3.3, the collections module added collections.ChainMap. Utilizing this class can simplify your code.
  • You should probably make self.a_ids and self.b_ids a list. And index to get which one you want, such as self.ids[0]. This list is created when using ChainMap anyway too.

And so I'd change your code to something more like:

from collections import ChainMap


class UserList:
    def __init__(self, users):
        self._lookup = ChainMap({}, {})
        self.ids = self._lookup.maps
        for user in users:
            self.ids[0][user['a_id']] = user
            self.ids[1][user['b_id']] = user

    def __contains__(self, some_id):
        return some_id in self._lookup

    def __getitem__(self, some_id):
        return self._lookup[some_id]

Alternately, you could subclass ChainMap, and not need to write any of the methods.

from collections import ChainMap


class UserList(ChainMap):
    def __init__(self, users):
        super().__init__({}, {})
        for user in users:
            self.maps[0][user['a_id']] = user
            self.maps[1][user['b_id']] = user

However, at that point, there's not much benefit to using a class. And so I'd just use a raw ChainMap.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.