Python Cookbook 2Nd Edition Jun 1002005 [Electronic resources]

David Ascher, Alex Martelli, Anna Ravenscroft

نسخه متنی -صفحه : 394/ 140
نمايش فراداده

Recipe 6.4. Chaining Dictionary Lookups

Credit: Raymond Hettinger

Problem

You have several mappings (usually dicts) and want to look things up in them in a chained way (try the first one; if the key is not there, then try the second one; and so on). Specifically, you want to make a single mapping object that "virtually merges" several others, by looking things up in them in a specified priority order, so that you can conveniently pass that one object around.

Solution

A mapping is a generalized, abstract version of a dictionary: a mapping provides an interface that's similar to a dictionary's, but it may use very different implementations. All dictionaries are mappings, but not vice versa. Here, you need to implement a mapping which sequentially tries delegating lookups to other mappings. A class is the right way to encapsulate this functionality:

class Chainmap(object):
def _ _init_ _(self, *mappings):
# record the sequence of mappings into which we must look
self._mappings = mappings
def _ _getitem_ _(self, key):
# try looking up into each mapping in sequence
for mapping in self._mappings:
try:
return mapping[key]
except KeyError:
pass
# `key' not found in any mapping, so raise KeyError exception
raise KeyError, key
def get(self, key, default=None):
# return self[key] if present, otherwise `default'
try:
return self[key]
except KeyError:
return default
def _ _contains_ _(self, key):
# return True if `key' is present in self, otherwise False
try:
self[key]
return True
except KeyError:
return False

For example, you can now implement the same sequence of lookups that Python normally uses for any name: look among locals, then (if not found there) among globals, lastly (if not found yet) among built-ins:

import _ _builtin_ _
pylookup = Chainmap(locals( ), globals( ), vars(_ _builtin_ _))

Discussion

Chainmap relies on minimal functionality from the mappings it wraps: each of those underlying mappings must allow indexing (i.e., supply a special method _ _getitem_ _), and it must raise the standard exception KeyError when indexed with a key that the mapping does not know about. A Chainmap instance provides the same behavior, plus the handy get method covered in Recipe 4.9 and special method _ _contains_ _ (which conveniently lets you check whether some key k is present in a Chainmap instance c by just coding if k in c).

Besides the obvious and sensible limitation of being "read-only", this Chainmap class has othersessentially, it is not a "full mapping" even within the read-only design choice. You can make any partial mapping into a "full mapping" by inheriting from class DictMixin (in standard library module UserDict) and supplying a few key methods (DictMixin implements the others). Here is how you could make a full (read-only) mapping from ChainMap and UserDict.DictMixin:

import UserDict
from sets import Set
class FullChainmap(Chainmap, UserDict.DictMixin):
def copy(self):
return self._ _class_ _(self._mappings)
def _ _iter_ _(self):
seen = Set( )
for mapping in self._mappings:
for key in mapping:
if key not in seen:
yield key
seen.add(key)
iterkeys = _ _iter_ _
def keys(self):
return list(self)

This class FullChainmap adds one requirement to the mappings it holds, besides the requirements posed by Chainmap: the mappings must be iterable. Also note that the implementation in Chainmap of methods get and _ _contains_ _ is redundant (although innocuous) once we subclass DictMixin, since DictMixin also implements those two methods (as well as many others) in terms of lower-level methods, just like Chainmap does. See Recipe 5.14 for more details about DictMixin.

See Also

Recipe 4.9; Recipe 5.14; the Library Reference and Python in a Nutshell sections on mapping types.