特殊字典---python

在阅读requests源码的过程中,发现KR建立了一个structures.py文件,打开一看,里面都是一些特殊的字典,看了一下实现,感觉挺有意思的,和大家分享一下收获TypeConversionDictMultiDict,是来自werkzeug的两个特殊的数据结构。

TypeConversionDict

TypeConversionDict 要实现的功能如下:

    >>> d = TypeConversionDict(foo='42', bar='blub')
    >>> d.get('foo', type=int)
    42
    >>> d.get('bar', -1, type=int)
    -1

挺简单的,获取字典的值,并对其进行类型转换。

class TypeConversionDict(dict):
    def get(self, key, default=None, type=None):
        try:
            rv = self[key]
            if type is not None:
                rv = type(rv)
        except (KeyError, ValueError):
            rv = default
        return rv

MultiDict

MultiDict 主要功能如下:

>>> d = MultiDict([('a', 'b'), ('a', 'c')])
>>> d
MultiDict([('a', 'b'), ('a', 'c')])
>>> d['a']
'b'
>>> d.getlist('a')
['b', 'c']
>>> 'a' in d
True

思考一下,如何实现:将一个键对应的值保存为 list,标准的 dict 访问方法将只返回键的第一个值。

初始化

考虑到数据的来源,初始化的时候分为三种情况分别处理,最后都成为{key,list}形式。例如: mapping = [(‘a’, ‘b’), (‘a’, ‘c’),(‘b’,‘d’)] 会被处理成 {‘a’: [‘b’, ‘c’], ‘b’: [‘d’]}。

    def __init__(self, mapping=None):
        if isinstance(mapping, MultiDict):
            dict.__init__(self, ((k, l[:]) for k, l in mapping.iterlists()))
        elif isinstance(mapping, dict):
            tmp = {}
            for key, value in mapping.iteritems():
                if isinstance(value, (tuple, list)):
                    value = list(value)
                else:
                    value = [value]
                tmp[key] = value
            dict.__init__(self, tmp)
        else:
            tmp = {}
            for key, value in mapping or ():
                tmp.setdefault(key, []).append(value)
            dict.__init__(self, tmp)

当实例对象通过[] 运算符取值时,会调用它的方法__getitem__,我们希望实现的是MultiDict的实例取值时,返回对应列表中的第一个,要重写__getitem__函数。

    def __getitem__(self, key):
        if key in self:
            return dict.__getitem__(self, key)[0]
        raise self.KeyError(key)

getlist 按照我自己的想法,直接返回键对应的list,但作者在这里,遍历了rv,对其中的每一个值尝试进行类型转换,考虑更全面。

    def getlist(self, key, type=None):
        try:
            rv = dict.__getitem__(self, key)
        except KeyError:
            return []
        if type is None:
            return list(rv)
        result = []
        for item in rv:
            try:
                result.append(type(item))
            except ValueError:
                pass
        return result

_getstate_ 与 _setstate_ 主要是改变对象的状态,让其能够进行 pickle

    def __getstate__(self):
        return dict(self.lists())

    def __setstate__(self, value):
        dict.clear(self)
        dict.update(self, value)

整个源码如下,作者的注释写的很清晰

class MultiDict(TypeConversionDict):
    # the key error this class raises.  Because of circular dependencies
    # with the http exception module this class is created at the end of
    # this module.
    KeyError = None

    def __init__(self, mapping=None):
        if isinstance(mapping, MultiDict):
            dict.__init__(self, ((k, l[:]) for k, l in mapping.iterlists()))
        elif isinstance(mapping, dict):
            tmp = {}
            for key, value in mapping.iteritems():
                if isinstance(value, (tuple, list)):
                    value = list(value)
                else:
                    value = [value]
                tmp[key] = value
            dict.__init__(self, tmp)
        else:
            tmp = {}
            for key, value in mapping or ():
                tmp.setdefault(key, []).append(value)
            dict.__init__(self, tmp)

    def __getstate__(self):
        return dict(self.lists())

    def __setstate__(self, value):
        dict.clear(self)
        dict.update(self, value)

    def __iter__(self):
        return self.iterkeys()

    def __getitem__(self, key):
        """Return the first data value for this key;
        raises KeyError if not found.

        :param key: The key to be looked up.
        :raise KeyError: if the key does not exist.
        """
        if key in self:
            return dict.__getitem__(self, key)[0]
        raise self.KeyError(key)

    def __setitem__(self, key, value):
        """Like :meth:`add` but removes an existing key first.

        :param key: the key for the value.
        :param value: the value to set.
        """
        dict.__setitem__(self, key, [value])

    def add(self, key, value):
        """Adds a new value for the key.

        .. versionadded:: 0.6

        :param key: the key for the value.
        :param value: the value to add.
        """
        dict.setdefault(self, key, []).append(value)

    def getlist(self, key, type=None):
        """Return the list of items for a given key. If that key is not in the
        `MultiDict`, the return value will be an empty list.  Just as `get`
        `getlist` accepts a `type` parameter.  All items will be converted
        with the callable defined there.

        :param key: The key to be looked up.
        :param type: A callable that is used to cast the value in the
                     :class:`MultiDict`.  If a :exc:`ValueError` is raised
                     by this callable the value will be removed from the list.
        :return: a :class:`list` of all the values for the key.
        """
        try:
            rv = dict.__getitem__(self, key)
        except KeyError:
            return []
        if type is None:
            return list(rv)
        result = []
        for item in rv:
            try:
                result.append(type(item))
            except ValueError:
                pass
        return result

    def setlist(self, key, new_list):
        """Remove the old values for a key and add new ones.  Note that the list
        you pass the values in will be shallow-copied before it is inserted in
        the dictionary.

        >>> d = MultiDict()
        >>> d.setlist('foo', ['1', '2'])
        >>> d['foo']
        '1'
        >>> d.getlist('foo')
        ['1', '2']

        :param key: The key for which the values are set.
        :param new_list: An iterable with the new values for the key.  Old values
                         are removed first.
        """
        dict.__setitem__(self, key, list(new_list))

    def setdefault(self, key, default=None):
        """Returns the value for the key if it is in the dict, otherwise it
        returns `default` and sets that value for `key`.

        :param key: The key to be looked up.
        :param default: The default value to be returned if the key is not
                        in the dict.  If not further specified it's `None`.
        """
        if key not in self:
            self[key] = default
        else:
            default = self[key]
        return default

    def setlistdefault(self, key, default_list=None):
        """Like `setdefault` but sets multiple values.  The list returned
        is not a copy, but the list that is actually used internally.  This
        means that you can put new values into the dict by appending items
        to the list:

        >>> d = MultiDict({"foo": 1})
        >>> d.setlistdefault("foo").extend([2, 3])
        >>> d.getlist("foo")
        [1, 2, 3]

        :param key: The key to be looked up.
        :param default: An iterable of default values.  It is either copied
                        (in case it was a list) or converted into a list
                        before returned.
        :return: a :class:`list`
        """
        if key not in self:
            default_list = list(default_list or ())
            dict.__setitem__(self, key, default_list)
        else:
            default_list = dict.__getitem__(self, key)
        return default_list

    def items(self, multi=False):
        """Return a list of ``(key, value)`` pairs.

        :param multi: If set to `True` the list returned will have a
                      pair for each value of each key.  Otherwise it
                      will only contain pairs for the first value of
                      each key.

        :return: a :class:`list`
        """
        return list(self.iteritems(multi))

    def lists(self):
        """Return a list of ``(key, values)`` pairs, where values is the list of
        all values associated with the key.

        :return: a :class:`list`
        """
        return list(self.iterlists())

    def values(self):
        """Returns a list of the first value on every key's value list.

        :return: a :class:`list`.
        """
        return [self[key] for key in self.iterkeys()]

    def listvalues(self):
        """Return a list of all values associated with a key.  Zipping
        :meth:`keys` and this is the same as calling :meth:`lists`:

        >>> d = MultiDict({"foo": [1, 2, 3]})
        >>> zip(d.keys(), d.listvalues()) == d.lists()
        True

        :return: a :class:`list`
        """
        return list(self.iterlistvalues())

    def iteritems(self, multi=False):
        """Like :meth:`items` but returns an iterator."""
        for key, values in dict.iteritems(self):
            if multi:
                for value in values:
                    yield key, value
            else:
                yield key, values[0]

    def iterlists(self):
        """Like :meth:`items` but returns an iterator."""
        for key, values in dict.iteritems(self):
            yield key, list(values)

    def itervalues(self):
        """Like :meth:`values` but returns an iterator."""
        for values in dict.itervalues(self):
            yield values[0]

    def iterlistvalues(self):
        """Like :meth:`listvalues` but returns an iterator."""
        return dict.itervalues(self)

    def copy(self):
        """Return a shallow copy of this object."""
        return self.__class__(self)

    def to_dict(self, flat=True):
        """Return the contents as regular dict.  If `flat` is `True` the
        returned dict will only have the first item present, if `flat` is
        `False` all values will be returned as lists.

        :param flat: If set to `False` the dict returned will have lists
                     with all the values in it.  Otherwise it will only
                     contain the first value for each key.
        :return: a :class:`dict`
        """
        if flat:
            return dict(self.iteritems())
        return dict(self.lists())

    def update(self, other_dict):
        """update() extends rather than replaces existing key lists."""
        for key, value in iter_multi_items(other_dict):
            MultiDict.add(self, key, value)

    def pop(self, key, default=_missing):
        """Pop the first item for a list on the dict.  Afterwards the
        key is removed from the dict, so additional values are discarded:

        >>> d = MultiDict({"foo": [1, 2, 3]})
        >>> d.pop("foo")
        1
        >>> "foo" in d
        False

        :param key: the key to pop.
        :param default: if provided the value to return if the key was
                        not in the dictionary.
        """
        try:
            return dict.pop(self, key)[0]
        except KeyError, e:
            if default is not _missing:
                return default
            raise self.KeyError(str(e))

    def popitem(self):
        """Pop an item from the dict."""
        try:
            item = dict.popitem(self)
            return (item[0], item[1][0])
        except KeyError, e:
            raise self.KeyError(str(e))

    def poplist(self, key):
        """Pop the list for a key from the dict.  If the key is not in the dict
        an empty list is returned.

        .. versionchanged:: 0.5
           If the key does no longer exist a list is returned instead of
           raising an error.
        """
        return dict.pop(self, key, [])

    def popitemlist(self):
        """Pop a ``(key, list)`` tuple from the dict."""
        try:
            return dict.popitem(self)
        except KeyError, e:
            raise self.KeyError(str(e))

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.items(multi=True)

CaseInsensitiveDict

CaseInsensitiveDict 是一个不区分大小写的字典。

class CaseInsensitiveDict(dict):
    def _lower_keys(self):
        return list(map(str.lower, self.keys()))

    def __contains__(self, key):

        return key.lower() in self._lower_keys()

    def __getitem__(self, key):
        if key in self:
            return list(self.items())[self._lower_keys().index(key.lower())][1]


if __name__ == "__main__":
    data = {"x1": 1, "X2": 2, "x3": 3, "X4": 4}
    data = CaseInsensitiveDict(data)
    print("x2" in data)  # true
    print(data)
    print(data['X3'])  #3

阅读资料

  • Werkzeug文档

你可能感兴趣的:(源码之路)