站在巨人肩上看世界:通过开源项目学Python #P012#

在这篇文章中,将会讨论著名开源项目对Python内置dict的几种扩展。这些扩展本身是出于特定目的而开发,应用范围不如Python内置的dict广泛。但是,由于这些扩展本身很“小”,所以它的应用范围也不仅限于特定的开源项目,我们完全可以将这些扩展拿过来应用到自己的项目之中。

通过学习这些优秀开源项目的源码,不但可以开阔自己视野,还能达到提高Python编码水平的目的。

本文将依次介绍以下几个对Python字典的扩展:

  • Storage 封装字典操作提供便利性
  • ImmutiDict 一旦赋值、不可修改的字典
  • CaseSentiveDict Key大小写不敏感的通用字典

1 Storage

1.1 Storage的作用

Storage是web.py的作者为了更方便的使用字典,进行的简单封装。封装完成以后,既可以通过点运算符(d.a)访问字典的项,也可以通过Python内置的方式(d[‘a’])访问字典项。显然,使用Storage进行封装以后,访问字典可以少敲很多字符,这就是Storage提供的便利性。

1.2 Storage的源码

Storage的具体实现如下,为了便于大家更直观的感受Storage的用法,我对Storage的文档进行了简单修改:

class Storage(dict):
    """
    A Storage object is like a dictionary except `obj.foo` can be used
    in addition to `obj['foo']`.

        o = Storage(a=1, b=2, c=3)
        >>> print o
        <Storage {'a': 1, 'c': 3, 'b': 2}>

        >>> o.a = 2
        >>> print o
        <Storage {'a': 2, 'c': 3, 'b': 2}>

        >>> o.d = 4
        >>> print o
        <Storage {'a': 2, 'c': 3, 'b': 2, 'd': 4}>

        >>> del o.b
        >>> print o
        <Storage {'a': 2, 'c': 3, 'd': 4}>

        >>> o.b
        Traceback (most recent call last):
            ...
        AttributeError: 'b'
    """
    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError as k:
            raise AttributeError(k)

    def __setattr__(self, key, value):
        self[key] = value

    def __delattr__(self, key):
        try:
            del self[key]
        except KeyError as k:
            raise AttributeError(k)

    def __repr__(self):
        return '<Storage ' + dict.__repr__(self) + '>'

1.3 Storage的实现解析

Storage的实现非常简单,除去为了友好的显示提供的__repr__函数以外,仅包含三个与属性相关的特殊函数,分别是__getattr__, __setattr__, __delattr__。显然,这三个特殊函数就是实现字典的增删改查操作的特殊函数。Storage拦截了这三个操作,并重新应用到内置字典的操作中,以此来提供一个操作更加方便的字典。

可以看到,Storage的实现非常简单,但是,Storage确是一个 很好的教学项目:

  1. 可以通过Storage了解如何扩展内置字典;
  2. 通过Storage了解Python的运算符重载的应用。

2 ImmutableDict

ImmutableDict是werkzeug中的实现的不可变字典。werkzeug是WSGI工具集,也是Flask的依赖,由Flask的作者实现。Flask是目前Python生态中最流行的Web框架。

2.1 ImmutableDict的使用

ImmutableDict的使用很简单也很直接,就是创建一个不可修改的字典。字典一旦创建成功,就只能访问,不能进行任何修改操作,也不能删除字典中的元素。如下所示:

def main():

​ d = ImmutableDict(a=1, b=2, c=3)
​ print(d) # ImmutableDict({‘a’: 1, ‘c’: 3, ‘b’: 2})
​ # d[‘d’] = 4 # TypeError: ‘ImmutableDict’ objects are immutable
if name == ‘main’:
main()

2.2 ImmutableDict的实现

ImmutableDict的实现如下:

from __future__ import print_function


def is_immutable(self):
    raise TypeError('%r objects are immutable' % self.__class__.__name__)


class ImmutableDictMixin(object):

    """Makes a :class:`dict` immutable.
    .. versionadded:: 0.5
    :private:
    """
    _hash_cache = None

    @classmethod
    def fromkeys(cls, keys, value=None):
        instance = super(cls, cls).__new__(cls)
        instance.__init__(zip(keys, repeat(value)))
        return instance

    def __reduce_ex__(self, protocol):
        return type(self), (dict(self),)

    def _iter_hashitems(self):
        return iteritems(self)

    def __hash__(self):
        if self._hash_cache is not None:
            return self._hash_cache
        rv = self._hash_cache = hash(frozenset(self._iter_hashitems()))
        return rv

    def setdefault(self, key, default=None):
        is_immutable(self)

    def update(self, *args, **kwargs):
        is_immutable(self)

    def pop(self, key, default=None):
        is_immutable(self)

    def popitem(self):
        is_immutable(self)

    def __setitem__(self, key, value):
        is_immutable(self)

    def __delitem__(self, key):
        is_immutable(self)

    def clear(self):
        is_immutable(self)


class ImmutableDict(ImmutableDictMixin, dict):

    """An immutable :class:`dict`.
    .. versionadded:: 0.5
    """

    def __repr__(self):
        return '%s(%s)' % (
            self.__class__.__name__,
            dict.__repr__(self),
        )

    def copy(self):
        """Return a shallow mutable copy of this object.  Keep in mind that
        the standard library's :func:`copy` function is a no-op for this class
        like for any other python immutable type (eg: :class:`tuple`).
        """
        return dict(self)

    def __copy__(self):
        return self

2.3 ImmutableDict源码解析

首先,ImmutableDict用到了一个辅助函数,即is_immutable。这个函数的作用很简单,就是抛出异常,提示使用者ImmutableDict是不可修改的字典。

然后,读者可以看到,这里定义了两个类,分别是ImmutableDictMixin和ImmutableDict,其中,ImmutableDict继承自ImmutableDictMixin和Dict(注意继承的顺序)。

ImmutableDictMixin的实现也很简单,就是拦截所有对字典的修改操作,并且抛出异常。

ImmutableDict继承自ImmutableDictMixin和内置的dict,它就同时拥有了ImmutableDictMixin的功能和Python内置dict的功能。例如,对ImmutableDict执行一个删除操作,这个删除操作将首先在ImmutableDict中查找__delitem__函数。显然,ImmutableDict并没有提供这样一个函数。因此,Python会从上到下、从左往右继续进行查找。接下来将在ImmutableDictMixin类里面查找__delitem_的实现,如果还找不到,再到dict中去查找。由于ImmutableDictMixin实现了__delitem__,拦截了删除操作,而__delitem__的实现中,是抛出一个实现,所以,ImmutableDict成为了一个不可变的字典。

这种实现方式,在Python是一种广泛的设计方案,称之为Mix-in组件。Mix-in主要用以解决多重继承的问题,它定义了其他类可能需要的一套附加方法,稍后再将其应用到其他类上即可。灵活的使用Mix-in类,可以减少重复代码,提高代码复用。

Python语言虽然支持多重继承,但是,一般都不推荐使用。在Python中,我们会将简单的行为封装到mix-in组件里,然后通过继承多个mix-in的方式来实现复杂的功能。

3 CaseInsentiveDict

CaseInsentiveDict是著名开源项目requests中的一个数据结构,顾名思义,它是一个大小写不明感的字典。如果存入字典的两个key,除了大小写不一样,其他都一样,则会被认为是相同的key。

3.1 CaseInsensitiveDict的作用

CaseInsentiveDict除了key大小写不敏感以外,其他地方和Python内置的字典一模一样。下面是一个CaseInsensitiveDict使用的例子:

def main():
    d = CaseInsensitiveDict()
    d['Key1'] = 'val1'
    d['KEY2'] = 'val2'
    d['key2'] = 'val3'

    print(d['Key1']) # val1
    print(d['key1']) # val1
    print(d['KEY2']) # val2
    print(d['key2']) # val2


if __name__ == '__main__':
    main()

3.2 CaseInsensitiveDict的实现

下面是CaseInsensitiveDict的实现,为了便于阅读,删除了两个无关痛痒的函数。CaseInsensitiveDict的实现中,重点是__setitem__、__getitem__和__delitem__这几个函数的实现。

import collections
from collections import OrderedDict

"""
requests.structures
"""
class CaseInsensitiveDict(collections.MutableMapping):

    def __init__(self, data=None, **kwargs):
        self._store = OrderedDict()
        if data is None:
            data = {}
        self.update(data, **kwargs)

    def __setitem__(self, key, value):
        self._store[key.lower()] = (key, value)

    def __getitem__(self, key):
        return self._store[key.lower()][1]

    def __delitem__(self, key):
        del self._store[key.lower()]

    def __iter__(self):
        return (casedkey for casedkey, mappedvalue in self._store.values())

    def __len__(self):
        return len(self._store)

3.3 CaseInsensitiveDict源码解析

CaseInsensitiveDict的实现思想是这样的,对于一对(key, value),实际保存的是 (key.lower(), (key, value)),如下图所示:

image

CaseInsensitiveDict拦截了字典操作,并在字典操赋值时,实际保存了以key的小写形式为key,以(key, value)这个二元组为value。在获取一个元素的时候,先将key转换为小写,然后再去读取原始的(key, value)二元组。
在CaseInsensitiveDict的实现中,还继承了collections.MutableMapping,这是Python标准库中的一个抽象类。通过继承这个类,工程师在实现一个字典的时候,如果没有实现重要的成员函数,Python将会提示工程师去实现它。
可以看到,要实现一个大小写不敏感的字典是如此的简单。其实,如果读者自己去实现一个大小写不明感的字典,或许并不简单。在requests的早期版本中,以另外一种完全不同的方式实现了大小写不敏感的字典,那个实现就不是特别直观,感兴趣的读者可以去看一下。

4 总结

学习编程都是从模仿高手的代码开始的,大多数工程师,阅读的代码量都太少太少了。因此,在大家学习遇到瓶颈,没有女朋友可以约会的节假日,都可以看看优秀开源项目的源代码。在Python生态中,我个人推荐requests与Flask的源码。笔者曾经还写过一篇Flask源码剖析的文章,希望对大家阅读Flask源码有所帮助。

作者介绍

赖明星,架构师、作家。现就职于腾讯,参与并主导下一代金融级数据库平台研发。有多年的 Python 开发经验和一线互联网实战经验,擅长 C、Python、Java、MySQL、Linux 等主流技术。国内知名的 Python 技术专家和 Python 技术的积极推广者,著有《Python Linux 系统管理与自动化运维》一书。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值