您的位置:新葡亰496net > 奥门新萄京娱乐场 > 新葡亰496net:python数据结构字典,字典和聚合

新葡亰496net:python数据结构字典,字典和聚合

发布时间:2019-06-20 12:39编辑:奥门新萄京娱乐场浏览(197)

    本文主要内容

    可散列类型

    泛映射类型

    字典

        (1)字典推导式

      (2)处理不存在的键

        (3)字典的变种

    集合

    映射的再讨论

     

    python高级——目录

    文中代码均放在github上:https://github.com/ampeeg/cnblogs/tree/master/python高级

     

    python高级(三)—— 字典和集合(泛映射类型),python映射

    本篇主要介绍:常见的字典方法、如何处理查不到的键、标准库中 dict 类型的变种、散列表的工作原理等。一下是全部内容:

    关于Python数据结构中字典的心得,python数据结构字典

    本篇主要介绍:常见的字典方法、如何处理查不到的键、标准库中 dict 类型的变种、散列表的工作原理等。一下是全部内容:

    泛映射类型

    collections.abc 模块中有 Mapping 和 MutableMapping 这两个抽象基类,它们的作用是为 dict 和其他类似的类型定义形式接口。

    新葡亰496net 1

    标准库里所有映射类型都是利用 dict 来实现的,它们有个共同的限制,即只有可散列的数据类型才能用做这些映射里的键。

    问题: 什么是可散列的数据类型?

    在 python 词汇表(

    如果一个对象是可散列的,那么在这个对象的生命周期中,它的散列值是不变的,而且这个对象需要实现 __hash__() 方法。另外可散列对象还要有 __eq__() 方法,这样才能跟其他键做比较。如果两个可散列对象是相等的,那么它们的散列只一定是一样的

    根据这个定义,原子不可变类型(str,bytes和数值类型)都是可散列类型,frozenset 也是可散列的(因为根据其定义,frozenset 里只能容纳可散列类型),如果元组内都是可散列类型的话,元组也是可散列的(元组虽然是不可变类型,但如果它里面的元素是可变类型,这种元组也不能被认为是不可变的)。

    一般来讲,用户自定义的类型的对象都是可散列的,散列值就是它们的 id() 函数的返回值,所以这些对象在比较的时候都是不相等的。(如果一个对象实现了 eq 方法,并且在方法中用到了这个对象的内部状态的话,那么只有当所有这些内部状态都是不可变的情况下,这个对象才是可散列的。)

    根据这些定义,字典提供了很多种构造方法, 这个页面有个例子来说明创建字典的不同方式。

    >>> a = dict(one=1, two=2, three=3)
    >>> b = {'one': 1, 'two': 2, 'three': 3}
    >>> c = dict(zip(['one', 'two', 'three'], [1, 2, 3]))
    >>> d = dict([('two', 2), ('one', 1), ('three', 3)])
    >>> e = dict({'three': 3, 'one': 1, 'two': 2})
    >>> a == b == c == d == e
    True
    

    除了这些方法以外,还可以用字典推导的方式来建造新 dict。

    字典推导

    自 Python2.7 以来,列表推导和生成器表达式的概念就移植到了字典上,从而有了字典推导。字典推导(dictcomp)可以从任何以键值对作为元素的可迭代对象中构建出字典。

    比如:

    >>> data = [(1, 'a'), (2, 'b'), (3, 'c')]
    >>> data_dict = {num: letter for num, letter in data}
    >>> data_dict
    {1: 'a', 2: 'b', 3: 'c'}
    

    常见的映射方法

    下表为我们展示了 dict、defaultdict 和 OrderedDict 的常见方法(后两种是 dict 的变种,位于 collections模块内)。

    新葡亰496net 2

    default_factory 并不是一个方法,而是一个可调用对象,它的值 defaultdict 初始化的时候由用户设定。 OrderedDict.popitem() 会移除字典最先插入的元素(先进先出);可选参数 last 如果值为真,则会移除最后插入的元素(后进先出)。用 setdefault 处理找不到的键

    当字典 d[k] 不能找到正确的键的时候,Python 会抛出异常,平时我们都使用d.get(k, default) 来代替 d[k],给找不到的键一个默认值,还可以使用效率更高的 setdefault

    my_dict.setdefault(key, []).append(new_value)
    # 等同于
    if key not in my_dict:
     my_dict[key] = []
    my_dict[key].append(new_value)
    

    这两段代码的效果一样,只不过,后者至少要进行两次键查询,如果不存在,就是三次,而用 setdefault 只需一次就可以完成整个操作。

    那么,我们取值的时候,该如何处理找不到的键呢?

    映射的弹性查询

    有时候,就算某个键在映射里不存在,我们也希望在通过这个键读取值的时候能得到一个默认值。有两个途径能帮我们达到这个目的,一个是通过 defaultdict 这个类型而不是普通的 dict,另一个是给自己定义一个 dict 的子类,然后在子类中实现 __missing__ 方法。

    defaultdict:处理找不到的键的一个选择

    首先我们看下如何使用 defaultdict :

    import collections
    
    index = collections.defaultdict(list)
    index[new_key].append(new_value)
    

    这里我们新建了一个字典 index,如果键 new_key 在 index 中不存在,表达式 index[new_key] 会按以下步骤来操作:

    调用 list() 来建立一个新的列表把这个新列表作为值,'new_key' 作为它的键,放入 index 中返回这个列表的引用。

    而这个用来生成默认值的可调用对象存放在名为 default_factory 的实例属性中。

    defaultdict 中的 default_factory 只会在 getitem 里调用,在其他方法中不会发生作用。比如 index[k] 这个表达式会调用 default_factory 创造的某个默认值,而 index.get(k) 则会返回 None。(这是因为特殊方法 missing 会在 defaultdict 遇到找不到的键的时候调用 default_factory,实际上,这个特性所有映射方法都可以支持)。

    特殊方法 missing

    所有映射在处理找不到的键的时候,都会牵扯到 missing 方法。但基类 dict 并没有提供 这个方法。不过,如果有一个类继承了 dict ,然后这个继承类提供了 missing 方法,那么在 getitem 碰到找不到键的时候,Python 会自动调用它,而不是抛出一个 KeyError 异常。

    __missing__ 方法只会被 __getitem__ 调用。提供 missing 方法对 get 或者 __contains__(in 运算符会用到这个方法)这些方法的是有没有影响。

    下面这段代码实现了 StrKeyDict0 类,StrKeyDict0 类在查询的时候把非字符串的键转化为字符串。

    class StrKeyDict0(dict): # 继承 dict
     def __missing__(self, key):
     if isinstance(key, str):
      # 如果找不到的键本身就是字符串,抛出 KeyError 
      raise KeyError(key)
     # 如果找不到的键不是字符串,转化为字符串再找一次
     return self[str(key)]
     def get(self, key, default=None):
     # get 方法把查找工作用 self[key] 的形式委托给 __getitem__,这样在宣布查找失败钱,还能通过 __missing__ 再给键一个机会
     try:
      return self[key]
     except KeyError:
      # 如果抛出 KeyError 说明 __missing__ 也失败了,于是返回 default 
      return default
     def __contains__(self, key):
     # 先按传入的键查找,如果没有再把键转为字符串再找一次
     return key in self.keys() or str(key) in self.keys()
    

    contains 方法存在是为了保持一致性,因为 k in d 这个操作会调用它,但我们从 dict 继承到的 contains 方法不会在找不到键的时候用 missing 方法。

    my_dict.keys() 在 Python3 中返回值是一个 "视图","视图"就像是一个集合,而且和字典一样速度很快。但在 Python2中,my_dict.keys() 返回的是一个列表。 所以 k in my_dict.keys() 操作在 python3中速度很快,但在 python2 中,处理效率并不高。

    如果要自定义一个映射类型,合适的策略是继承 collections.UserDict 类。这个类就是把标准 dict 用 python 又实现了一遍,UserDict 是让用户继承写子类的,改进后的代码如下:

    import collections
    
    class StrKeyDict(collections.UserDict):
    
     def __missing__(self, key):
     if isinstance(key, str):
      raise KeyError(key)
     return self[str(key)]
    
     def __contains__(self, key):
     # 这里可以放心假设所有已经存储的键都是字符串。因此只要在 self.data 上查询就好了
     return str(key) in self.data
    
     def __setitem__(self, key, item):
     # 这个方法会把所有的键都转化成字符串。
     self.data[str(key)] = item
    

    因为 UserDict 继承的是 MutableMapping,所以 StrKeyDict 里剩下的那些映射类型都是从 UserDict、MutableMapping 和 Mapping 这些超类继承而来的。

    Mapping 中提供了 get 方法,和我们在 StrKeyDict0 中定义的一样,所以我们在这里不需要定义 get 方法。

    字典的变种

    在 collections 模块中,除了 defaultdict 之外还有其他的映射类型。

    collections.OrderedDict collections.ChainMap collections.Counter 不可变的映射类型

    问题:标准库中所有的映射类型都是可变的,如果我们想给用户提供一个不可变的映射类型该如何处理呢?

    从 Python3.3 开始 types 模块中引入了一个封装类名叫 MappingProxyType。如果给这个类一个映射,它会返回一个只读的映射视图(如果原映射做了改动,这个视图的结果页会相应的改变)。例如

    >>> from types import MappingProxy Type
    >>> d = {1: 'A'}
    >>> d_proxy = MappingProxyType(d)
    >>> d_proxy
    mappingproxy({1: 'A'})
    >>> d_proxy[1]
    'A'
    >>> d_proxy[2] = 'x'
    Traceback(most recent call last):
     File "<stdin", line 1, in <module>
    TypeError: 'MappingProxy' object does not support item assignment
    >>> d[2] = 'B'
    >>> d_proxy[2] # d_proxy 是动态的,d 的改动会反馈到它上边
    'B'
    

    字典中的散列表

    散列表其实是一个稀疏数组(总有空白元素的数组叫稀疏数组),在 dict 的散列表中,每个键值都占用一个表元,每个表元都有两个部分,一个是对键的引用,另一个是对值的引用。因为所有表元的大小一致,所以可以通过偏移量来读取某个表元。
    python 会设法保证大概有1/3 的表元是空的,所以在快要达到这个阈值的时候,原有的散列表会被复制到一个更大的空间。

    如果要把一个对象放入散列表,那么首先要计算这个元素的散列值。
    Python内置的 hash() 方法可以用于计算所有的内置类型对象。

    如果两个对象在比较的时候是相等的,那么它们的散列值也必须相等。例如 1==1.0 那么,hash(1) == hash(1.0)

    散列表算法

    为了获取 my_dict[search_key] 的值,Python 会首先调用 hash(search_key) 来计算 search_key 的散列值,把这个值的最低几位当做偏移量在散列表中查找元。若表元为空,抛出 KeyError 异常。若不为空,则表元会有一对 found_key:found_value。
    这时需要校验 search_key == found_key,如果相等,返回 found_value。
    如果不匹配(散列冲突),再在散列表中再取几位,然后处理一下,用处理后的结果当做索引再找表元。 然后重复上面的步骤。

    取值流程图如下:

    新葡亰496net 3

    添加新值和上述的流程基本一致,只不过对于前者,在发现空表元的时候会放入一个新元素,而对于后者,在找到相应表元后,原表里的值对象会被替换成新值。

    另外,在插入新值是,Python 可能会按照散列表的拥挤程度来决定是否重新分配内存为它扩容,如果增加了散列表的大小,那散列值所占的位数和用作索引的位数都会随之增加字典的优势和限制

    1、键必须是可散列的

    可散列对象要求如下:

    支持 hash 函数,并且通过__hash__() 方法所得的散列值不变支持通过 __eq__() 方法检测相等性若 a == b 为真, 则 hash(a) == hash(b) 也为真

    2、字典开销巨大

    因为字典使用了散列表,而散列表又必须是稀疏的,这导致它在空间上效率低下。

    3、键查询很快

    dict 的实现是典型的空间换时间:字典类型由着巨大的内存开销,但提供了无视数据量大小的快速访问。

    4、键的次序决定于添加顺序

    当往 dict 里添加新键而又发生散列冲突时,新建可能会被安排存放在另一个位置。

    5、往字典里添加新键可能会改变已有键的顺序

    无论何时向字典中添加新的键,Python 解释器都可能做出为字典扩容的决定。扩容导致的结果就是要新建一个更大的散列表,并把原有的键添加到新的散列表中,这个过程中可能会发生新的散列冲突,导致新散列表中次序发生变化。
    因此,不要对字典同时进行迭代和修改。

    本篇主要介绍:常见的字典方法、如何处理查不到的键、标准库中 dict 类型的变种、散...

    泛映射类型

    可散列类型

    '''
        可散列数据类型(也称可hash)————我理解"可散列"就是"可hash"
        可hash的对象需要实现__hash__方法,返回hash值;另外为了与其他对象比较还需要有__eq__方法
    
        原子不可变数据类型(str、bytes和数值类型)都是可散列的,可散列对象必须满足下列要求:
        (1)实现了__hash__方法,并且所得到的hash值是不变的
        (2)实现了__eq__方法,用来比较
        (3)若a == b 为真,那么hash(a) == hash(b)也是真
    '''
    
    
    # 创建类Foo,并实现__hash__和__eq__
    
    class Foo:
        def __init__(self, name):
            self.name = name
    
        def __hash__(self):
            print("正在hash...")
            return hash(self.name)
    
        def __eq__(self, other):
            print("正在比较...")
            return self.name == other.name
    
        def __repr__(self):
            return self.name
    
    
    if __name__ == "__main__":
    
        f1 = Foo("小李")
        f2 = Foo("小红")
        f3 = Foo("小李")
    
        s = set([f1, f2, f3])        # 集合实现不重复的原理正好利用了散列表
        print(s)                     # {小红, 小李}
        print( f1 == f3, hash(f1) == hash(f3))      # True True 满足可散列对象的第三个条件
    '''
        对于元组来说,只有当一个元组包含的所有元素都是可hash的情况下,它才是可hash的
    '''
    t1 = (1, 2, 3, [1, 2])   # 元组里的列表的值是可变的,所以不可hash
    try:
        print(hash(t1))
    except Exception as e:
        print(e)             # unhashable type: 'list'
    
    t2 = (1, 2, 3, (1, 2))   # 元组里的元素都是不可变的,并且第二层元组里面的元素也不可变,所以可hash
    print(hash(t2))          # 3896079550788208169
    
    t3 = (1, 2, 3, frozenset([1, 2]))
    print(hash(t3))          # -5691000848003037416

     

    本文主要内容

    可散列类型

    泛映射类型

    字典

        (1)字典推导式

      (2)处理不存在的键

    集合

    映射的再讨论

     

    python高级——目录

    文中代码均放在github上:

     

    泛映射类型

    映射类型:不仅仅是dict,标准库里的所有映射类型都是利用dict来实现的,因此它们有个共同的限制,即只有可散列的数据类型才能用做这些映射的键。(只有键有这个需求,值并不需要必须是可散列的数据类型。)

    泛映射类型

    '''
        泛映射类型就是广义上的对应关系,在数学中,我们将集合A对应集合B中的对应法则称为"映射"(Mapping)
        同样,在python里,我们称"键值对"为映射,这其实也是一种对应法则
        如果一个数据类型是映射,那么它肯定属于collections.abc.Mapping,可使用isinstance函数测试
    
        PS: 字典是 Python 语言中唯一的映射类型。映射类型对象里哈希值(键) 和指向的对象(值)是一对多的关系。
    '''
    
    from collections import abc
    
    # 我们测试一些常用的类型是不是映射
    if __name__ == "__main__":
        print(isinstance({}, abc.Mapping))      # True   字典是典型的键值对
        print(isinstance([1, 2], abc.Mapping))  # False  列表是序列
        print(isinstance((1, 2), abc.Mapping))  # False  元组是序列
        print(isinstance('adfasfd', abc.Mapping))  # False  字符串也是序列
    '''
       大家可以查看_collections_abc.py源代码,里面基本的类型包含:
        ["Awaitable", "Coroutine", "AsyncIterable", "AsyncIterator",
        "Hashable", "Iterable", "Iterator", "Generator",
        "Sized", "Container", "Callable",
         "Set", "MutableSet",
         "Mapping", "MutableMapping",
         "MappingView", "KeysView", "ItemsView", "ValuesView",
         "Sequence", "MutableSequence",
        "ByteString",
        ]
    '''

     

    '''
        如果我们自己想定义一个映射类型的对象,那么必须实现__getitem__、__iter__、__len__方法
    
        PS:关于该部分的原理,本人暂未查看说明文档,毕竟现实中几乎不可能自定义映射;有兴趣的同志可深入钻研。
    '''
    
    
    class Foo(abc.Mapping):
        def __init__(self, name):
            self.name = name
    
        def __getitem__(self, item):
            return self.name
    
        def __iter__(self):
            return iter(str(self.name))
    
        def __len__(self):
            return len(self.name)
    
    
    print(isinstance(Foo("123"), abc.Mapping))      # True

     

    可散列类型

    '''
        可散列数据类型(也称可hash)————我理解"可散列"就是"可hash"
        可hash的对象需要实现__hash__方法,返回hash值;另外为了与其他对象比较还需要有__eq__方法
    
        原子不可变数据类型(str、bytes和数值类型)都是可散列的,可散列对象必须满足下列要求:
        (1)实现了__hash__方法,并且所得到的hash值是不变的
        (2)实现了__eq__方法,用来比较
        (3)若a == b 为真,那么hash(a) == hash(b)也是真
    '''
    
    
    # 创建类Foo,并实现__hash__和__eq__
    
    class Foo:
        def __init__(self, name):
            self.name = name
    
        def __hash__(self):
            print("正在hash...")
            return hash(self.name)
    
        def __eq__(self, other):
            print("正在比较...")
            return self.name == other.name
    
        def __repr__(self):
            return self.name
    
    
    if __name__ == "__main__":
    
        f1 = Foo("小李")
        f2 = Foo("小红")
        f3 = Foo("小李")
    
        s = set([f1, f2, f3])        # 集合实现不重复的原理正好利用了散列表
        print(s)                     # {小红, 小李}
        print( f1 == f3, hash(f1) == hash(f3))      # True True 满足可散列对象的第三个条件
    '''
        对于元组来说,只有当一个元组包含的所有元素都是可hash的情况下,它才是可hash的
    '''
    t1 = (1, 2, 3, [1, 2])   # 元组里的列表的值是可变的,所以不可hash
    try:
        print(hash(t1))
    except Exception as e:
        print(e)             # unhashable type: 'list'
    
    t2 = (1, 2, 3, (1, 2))   # 元组里的元素都是不可变的,并且第二层元组里面的元素也不可变,所以可hash
    print(hash(t2))          # 3896079550788208169
    
    t3 = (1, 2, 3, frozenset([1, 2]))
    print(hash(t3))          # -5691000848003037416

     

    collections.abc 模块中有 Mapping 和 MutableMapping 这两个抽象基类,它们的作用是为 dict 和其他类似的类型定义形式接口。

    什么是可散列的数据类型?

    字典

    '''
        字典是python内置类型中唯一的映射,先看创建字典的几种方法
    
        1、对象创建
        2、大括号
        3、zip
    '''
    
    if __name__ == "__main__":
        # 1、利用实例化对象的方法创建
        a = dict(key1=1, key2=2, all=[1, 2, 3])
        b = dict([('key3', 3), ('key4', 4)])
        c = dict({"key5": 5, "key6": 6})
    
        print("a:", a)     # a: {'key1': 1, 'all': [1, 2, 3], 'key2': 2}
        print("b:", b)     # b: {'key3': 3, 'key4': 4}
        print("c:", c)     # c: {'key6': 6, 'key5': 5}
    
        # 2、直接使用大括号
        d = {"key7": 7, "key8": 8}
        print("d:", d)     # d: {'key8': 8, 'key7': 7}
    
        # 3、使用zip
        e = dict(zip(("key9", "key10", "key11"), [9, 10, 11]))
        print("e:", e)     # e: {'key11': 11, 'key10': 10, 'key9': 9}

    (1)字典推导式

     

    '''
        字典推导式:字典推导式的创建方法同列表推导式类似
    
        以下直接引用《流畅的python》中的例子
    '''
    
    
    if __name__ == "__main__":
        DIAL_CODES = [
            (86, 'China'),
            (91, 'India'),
            (1, 'United States'),
            (62, 'Indonesia'),
            (55, 'Brazil'),
            (92, 'Pakistan'),
            (880, 'Bangladesh'),
            (234, 'Nigeria'),
            (7, 'Russia'),
            (81, 'Japan'),
        ]
    
        country_code = {country: code for code, country in DIAL_CODES}
        print(country_code)   # {'Russia': 7, 'Indonesia': 62, 'Brazil': 55, 'China': 86, 'India': 91, 'Bangladesh': 880, 'Pakistan': 92, 'United States': 1, 'Nigeria': 234, 'Japan': 81}
    
        code_upper = {code: country.upper() for country, code in country_code.items() if code < 66}
        print(code_upper)     # {1: 'UNITED STATES', 7: 'RUSSIA', 62: 'INDONESIA', 55: 'BRAZIL'}
    (2)处理不存在的键
    
    '''
        处理找不到的键
    
        在实际场景中,当使用d[key]的方法查找数据的时候,如果找不到该键,python会抛出KeyError异常;
        如果是取值操作,可以使用d.get(key, default)来解决,可以给找不到的键一个默认的值
        但是如果要给更新某个不存在键对应的值的时候,就稍显麻烦了,可以使用以下方法解决:
            1、用setdefault处理dict找不到的键
            2、使用defaultdict对象
            3、__missing__方法
    '''
    
    class Foo:
        def __init__(self, name=None):
            self.name = name
    
        def __repr__(self):
            return str(self.name)
    
        def setattr(self, key, value):
            self.__setattr__(key, value)
            return self
    
    
    if __name__ == "__main__":
        d1 = {}
        print(d1.get("key", "default"))   # default   使用d.get(key, default)的方法取值
    
    
        # 1、用setdefault处理dict找不到的键
        d2 = {}
        d2.setdefault("key", [x for x in "adfaf"])  # setdefault虽然是set名字,但是是取值操作,只有当键不存在时才进行赋值,并返回该值
        l = d2.setdefault("key", [])
        print(l)                                    # ['a', 'd', 'f', 'a', 'f']
    
        d2.setdefault("key2", []).extend([1, 2, 3]) # 返回空列表,所以可在后面直接使用方法extend
        print(d2)                                   # {'key': 'default', 'key2': [1, 2, 3]}
    
        # 2、使用defaultdict对象
        #  在python中,还有一些dict的变种类型,defaultdict为其中一种,位于collections中
        from collections import defaultdict
    
        dic = defaultdict(list)                    # 将list的构造方法作为default_factory(只有__getitem__找不到值时调用)
        dic["key"].extend([1, 2, 3])               # dic中不含有"key"键,此时default_factory会被调用,创造一个空列表,并连接[1, 2, 3]
        print(dic["key"])                # [1, 2, 3]
    
        dic = defaultdict(Foo)           # 将Foo的构造方法作为default_factory创建一个defaultdict
        print(dic["key"].setattr("name", "default"))                # default
    
        # 3、__missing__方法
        # 所有的映射类型在找不到键的时候,都会牵扯到__missing__方法;如果在__getitem__找不到键的时候,python就会自动调用它
        # 另外,__missing__方法只会被getitem调用,对get或者__contains__没有影响
    
        class My_dict(dict):
            def __missing__(self, key):
                print("正在调用__missing__...")
    
        mdict = My_dict(one=1, two=2, three=3)
        print(mdict)     # {'two': 2, 'three': 3, 'one': 1}
        mdict["key"]     # 正在调用__missing__...
    (3)字典的变种
    
    '''
        在python中虽然只有dict为映射类型,但是dict有很多变种,上面defaultdict就是,除此之外还有:
    
        (1)OrderedDict: 有顺序的字典
         (2) ChainMap: 可以容纳数个不同的映射对象
         (3) Counter:  给键准备一个整数计数器,每次更新键的时候会增加该计数器
        (4)UserDict:  将标准的dict用python实现了一遍
    '''
    
    
    from collections import OrderedDict, ChainMap, Counter, UserDict
    
    if __name__ == "__main__":
        # 1、OrderedDict
        d = OrderedDict()
        d['one'] = 1
        d['two'] = 2
        d['three'] = 3
        for _ in range(10):
            print("%d次:" % _)
            for k, v in d.items():
                print("**", k, v)        # OrderedDict迭代的时候的顺序总是跟插入顺序一致
    
    
        # 2、ChainMap
    
        pylookup = ChainMap(d, globals())   # d和globals()都是映射类型,ChainMap会将其组合
        for v, k in pylookup.items():
            print(v, k)
    
        # 3、Counter
        ct = Counter('asfjlajslfjals')
        print(ct)      # Counter({'j': 3, 'l': 3, 's': 3, 'a': 3, 'f': 2})
                       # 存储的是每个字母出现的次数
        ct.update('jjjjjjjjlllllllll')
        print(ct)      # # Counter({'l': 12, 'j': 11, 's': 3, 'a': 3, 'f': 2})
    
        import random
        ct2 = Counter([random.randrange(1, 5) for _ in range(100)])   # 列表推导式创建Counter
        print(ct2)     # Counter({1: 30, 2: 24, 4: 24, 3: 22})
    
        ct3 = Counter((random.randrange(1, 5) for _ in range(100)))   # 生成器创建Counter
        print(ct3)      # Counter({2: 40, 3: 23, 4: 20, 1: 17})
    
        class Foo:
            def __init__(self, num):
                self.l = [random.randrange(1, 5) for _ in range(num)]
    
            def __iter__(self):
                return iter(self.l)
    
        ct4 = Counter(Foo(100))            # 可迭代对象创建Counter
        print(ct4)      # Counter({2: 31, 3: 25, 4: 25, 1: 19})
    
        # 4、UserDict
        # 创建自定义的映射类型,一般以UserDict为基类
    
        class My_dict(UserDict):
            def __missing__(self, key):
                if isinstance(key, str):
                    raise KeyError(key)
                return self[str(key)]
    
            def __contains__(self, key):
                return str(key) in self.data
    
            def __setitem__(self, key, item):
                print("调用__setitem__。。。")
                self.data[str(key)] = item
    
        mdict = My_dict()
        mdict["one"] = 1      # 调用__setitem__。。。(下同)
        mdict["two"] = 2
        mdict["three"] = 3
        print(mdict)   # {'three': 3, 'one': 1, 'two': 2}

     

    泛映射类型

    '''
        泛映射类型就是广义上的对应关系,在数学中,我们将集合A对应集合B中的对应法则称为"映射"(Mapping)
        同样,在python里,我们称"键值对"为映射,这其实也是一种对应法则
        如果一个数据类型是映射,那么它肯定属于collections.abc.Mapping,可使用isinstance函数测试
    
        PS: 字典是 Python 语言中唯一的映射类型。映射类型对象里哈希值(键) 和指向的对象(值)是一对多的关系。
    '''
    
    from collections import abc
    
    # 我们测试一些常用的类型是不是映射
    if __name__ == "__main__":
        print(isinstance({}, abc.Mapping))      # True   字典是典型的键值对
        print(isinstance([1, 2], abc.Mapping))  # False  列表是序列
        print(isinstance((1, 2), abc.Mapping))  # False  元组是序列
        print(isinstance('adfasfd', abc.Mapping))  # False  字符串也是序列
    '''
       大家可以查看_collections_abc.py源代码,里面基本的类型包含:
        ["Awaitable", "Coroutine", "AsyncIterable", "AsyncIterator",
        "Hashable", "Iterable", "Iterator", "Generator",
        "Sized", "Container", "Callable",
         "Set", "MutableSet",
         "Mapping", "MutableMapping",
         "MappingView", "KeysView", "ItemsView", "ValuesView",
         "Sequence", "MutableSequence",
        "ByteString",
        ]
    '''

     

    '''
        如果我们自己想定义一个映射类型的对象,那么必须实现__getitem__、__iter__、__len__方法
    
        PS:关于该部分的原理,本人暂未查看说明文档,毕竟现实中几乎不可能自定义映射;有兴趣的同志可深入钻研。
    '''
    
    
    class Foo(abc.Mapping):
        def __init__(self, name):
            self.name = name
    
        def __getitem__(self, item):
            return self.name
    
        def __iter__(self):
            return iter(str(self.name))
    
        def __len__(self):
            return len(self.name)
    
    
    print(isinstance(Foo("123"), abc.Mapping))      # True

     

    新葡亰496net 4

    可散列的对象在它的生命周期中,散列值是不变的,需要实现hash()方法。另外散列对象还要有eq()方法,这样才能跟其他键作比较。

    集合

    '''
        集合对于很多人并不陌生,中学阶段就已经接触过。集合具有:
        (1)确定性:每一个对象都能确定是不是某一集合的元素,没有确定性就不能成为集合
        (2)互异性:集合中任意两个元素都是不同的对象
        (3)无序性:{a,b,c}{c,b,a}是同一个集合
    
        在python中,set中的元素必须是可散列的,但set本身不可散列(但是frosenset是可散列的)
    
    
        另外:set实现了很多基础运算
        &(交集)、|(并集)、-(差集)
    '''
    
    
    if __name__ == "__main__":
        # 创建集合
        s1 = set([1, 2, 3])
        s2 = {1, 2, 3, 4}
        print(s1, s2)     # {1, 2, 3} {1, 2, 3, 4}
    
        # 集合推导式
        s3 = {x**2 for x in range(10)}
        print(s3)         # {0, 1, 64, 4, 36, 9, 16, 49, 81, 25}

     

    set的操作方法很多,本文截自<流畅的python>一书,如下三个表:
    
    表一:集合的数学方法
    

     

    表2:集合的比较运算

     

    表3:集合的其他运算

     

    字典

    '''
        字典是python内置类型中唯一的映射,先看创建字典的几种方法
    
        1、对象创建
        2、大括号
        3、zip
    '''
    
    if __name__ == "__main__":
        # 1、利用实例化对象的方法创建
        a = dict(key1=1, key2=2, all=[1, 2, 3])
        b = dict([('key3', 3), ('key4', 4)])
        c = dict({"key5": 5, "key6": 6})
    
        print("a:", a)     # a: {'key1': 1, 'all': [1, 2, 3], 'key2': 2}
        print("b:", b)     # b: {'key3': 3, 'key4': 4}
        print("c:", c)     # c: {'key6': 6, 'key5': 5}
    
        # 2、直接使用大括号
        d = {"key7": 7, "key8": 8}
        print("d:", d)     # d: {'key8': 8, 'key7': 7}
    
        # 3、使用zip
        e = dict(zip(("key9", "key10", "key11"), [9, 10, 11]))
        print("e:", e)     # e: {'key11': 11, 'key10': 10, 'key9': 9}
    '''
        字典推导式:字典推导式的创建方法同列表推导式类似
    
        以下直接引用《流畅的python》中的例子
    '''
    
    
    if __name__ == "__main__":
        DIAL_CODES = [
            (86, 'China'),
            (91, 'India'),
            (1, 'United States'),
            (62, 'Indonesia'),
            (55, 'Brazil'),
            (92, 'Pakistan'),
            (880, 'Bangladesh'),
            (234, 'Nigeria'),
            (7, 'Russia'),
            (81, 'Japan'),
        ]
    
        country_code = {country: code for code, country in DIAL_CODES}
        print(country_code)   # {'Russia': 7, 'Indonesia': 62, 'Brazil': 55, 'China': 86, 'India': 91, 'Bangladesh': 880, 'Pakistan': 92, 'United States': 1, 'Nigeria': 234, 'Japan': 81}
    
        code_upper = {code: country.upper() for country, code in country_code.items() if code < 66}
        print(code_upper)     # {1: 'UNITED STATES', 7: 'RUSSIA', 62: 'INDONESIA', 55: 'BRAZIL'}
    '''
        处理找不到的键
    
        在实际场景中,当使用d[key]的方法查找数据的时候,如果找不到该键,python会抛出KeyError异常;
        如果是取值操作,可以使用d.get(key, default)来解决,可以给找不到的键一个默认的值
        但是如果要给更新某个不存在键对应的值的时候,就稍显麻烦了,可以使用以下方法解决:
            1、用setdefault处理dict找不到的键
            2、使用defaultdict对象
            3、__missing__方法
    '''
    
    class Foo:
        def __init__(self, name=None):
            self.name = name
    
        def __repr__(self):
            return str(self.name)
    
        def setattr(self, key, value):
            self.__setattr__(key, value)
            return self
    
    
    if __name__ == "__main__":
        d1 = {}
        print(d1.get("key", "default"))   # default   使用d.get(key, default)的方法取值
    
    
        # 1、用setdefault处理dict找不到的键
        d2 = {}
        d2.setdefault("key", [x for x in "adfaf"])  # setdefault虽然是set名字,但是是取值操作,只有当键不存在时才进行赋值,并返回该值
        l = d2.setdefault("key", [])
        print(l)                                    # ['a', 'd', 'f', 'a', 'f']
    
        d2.setdefault("key2", []).extend([1, 2, 3]) # 返回空列表,所以可在后面直接使用方法extend
        print(d2)                                   # {'key': 'default', 'key2': [1, 2, 3]}
    
        # 2、使用defaultdict对象
        #  在python中,还有一些dict的变种类型,defaultdict为其中一种,位于collections中
        from collections import defaultdict
    
        dic = defaultdict(list)                    # 将list的构造方法作为default_factory(只有__getitem__找不到值时调用)
        dic["key"].extend([1, 2, 3])               # dic中不含有"key"键,此时default_factory会被调用,创造一个空列表,并连接[1, 2, 3]
        print(dic["key"])                # [1, 2, 3]
    
        dic = defaultdict(Foo)           # 将Foo的构造方法作为default_factory创建一个defaultdict
        print(dic["key"].setattr("name", "default"))                # default
    
        # 3、__missing__方法
        # 所有的映射类型在找不到键的时候,都会牵扯到__missing__方法;如果在__getitem__找不到键的时候,python就会自动调用它
        # 另外,__missing__方法只会被getitem调用,对get或者__contains__没有影响
    
        class My_dict(dict):
            def __missing__(self, key):
                print("正在调用__missing__...")
    
        mdict = My_dict(one=1, two=2, three=3)
        print(mdict)     # {'two': 2, 'three': 3, 'one': 1}
        mdict["key"]     # 正在调用__missing__...

     

    标准库里所有映射类型都是利用 dict 来实现的,它们有个共同的限制,即只有可散列的数据类型才能用做这些映射里的键。

    • 原子不可变数据类型都是可散列的(str,bytes和数值类型,frozenset) - dict,list是不可散列的

    映射的再讨论 

    '''
        python标准库里面的映射类型都是可变的,有时候需要使用不可变的映射,从python3.3开始,types模块中引入了
        MappingProxyType类,如果给这个类一个映射,那么它会返回这个映射的试图,该试图是动态的,原映射如果有改动
        可立即通过这个试图观察到,但是这个试图无法对该映射进行修改。
    '''
    from types import MappingProxyType
    
    if __name__ == "__main__":
        d = {'one':1, 'two':2, 'three':3}
        d_proxy = MappingProxyType(d)
        print(d_proxy)     # {'three': 3, 'two': 2, 'one': 1}
        print(d_proxy['one'])  # 1
        for k, v in d_proxy.items():
            print(k, v)
    
        #d_proxy['four'] = 4   # 报错:TypeError: 'mappingproxy' object does not support item assignment
        d['four'] = 4
        print(d_proxy)     # {'two': 2, 'three': 3, 'four': 4, 'one': 1}

     

      另外,《流畅的python》77页到80页对散列表算法以及字典、集合的效率、平时需要注意的问题进行了比较详细的探讨,建议严谨并有兴趣的同仁阅读,该部分内容对理解字典类型无比有益,场景中捉摸不透的莫名其妙的bug可能会迎刃而解。

       重要的结论摘录如下:

      (1)键必须是可散列的

      (2)字典在内存上的开销巨大

      (3)键查询很快

      (4)键的次序取决于添加顺序

      (5)往字典里添加新键可能会改变已有键的顺序

     

    python高级系列文章目录

    python高级——目录

     

     

     

    字典和集合(泛映射类型),python映射 本文主要内容 可散列类型 泛映射类型 字典 (1)字典推导式 (2)处理不存在...

    问题: 什么是可散列的数据类型?

    用setdefault处理找不到的键

    python高级系列文章目录

    python高级——目录

     

     

     

    在 python 词汇表(

    当字典d[k]找不到值会抛出异常,通常我们使用d.get(k,default)来代替d[k],给找不到的键默认一个返回值。 但是要更新某个键对应的值的时候,不管是用getitem还是get都不太自然的,效率很低。

    如果一个对象是可散列的,那么在这个对象的生命周期中,它的散列值是不变的,而且这个对象需要实现 __hash__() 方法。另外可散列对象还要有 __eq__() 方法,这样才能跟其他键做比较。如果两个可散列对象是相等的,那么它们的散列只一定是一样的

    my_dict.setdefault.append(new_value)

    根据这个定义,原子不可变类型(str,bytes和数值类型)都是可散列类型,frozenset 也是可散列的(因为根据其定义,frozenset 里只能容纳可散列类型),如果元组内都是可散列类型的话,元组也是可散列的(元组虽然是不可变类型,但如果它里面的元素是可变类型,这种元组也不能被认为是不可变的)。

    if key not in my_dict:my_dict[key]=[]my_dict[key].append(new_value)两者的效果是一样的,只不过后者至少要进行两次查询——如果键不存在的话,就是三次,使用setdefault只需要一次就可以完成整个操作。

    一般来讲,用户自定义的类型的对象都是可散列的,散列值就是它们的 id() 函数的返回值,所以这些对象在比较的时候都是不相等的。(如果一个对象实现了 eq 方法,并且在方法中用到了这个对象的内部状态的话,那么只有当所有这些内部状态都是不可变的情况下,这个对象才是可散列的。)

    映射的弹性查询

    根据这些定义,字典提供了很多种构造方法, 这个页面有个例子来说明创建字典的不同方式。

    所谓的弹性查询就是,我找的键不在映射里面存在的时候,也能返回一个默认值比如

    >>> a = dict(one=1, two=2, three=3)
    >>> b = {'one': 1, 'two': 2, 'three': 3}
    >>> c = dict(zip(['one', 'two', 'three'], [1, 2, 3]))
    >>> d = dict([('two', 2), ('one', 1), ('three', 3)])
    >>> e = dict({'three': 3, 'one': 1, 'two': 2})
    >>> a == b == c == d == e
    True
    

    d.get(key,default)python有两个途径达到整个目的, - 一个是通过defaultdict这个类型而不是普通的dict - 另一个是给自己顶一个dict的子类,然后在子类中实现missing方法。

    除了这些方法以外,还可以用字典推导的方式来建造新 dict。

    defaultdict:处理找不到的键的一个选择

    字典推导

    dd = defaultdict

    自 Python2.7 以来,列表推导和生成器表达式的概念就移植到了字典上,从而有了字典推导。字典推导(dictcomp)可以从任何以键值对作为元素的可迭代对象中构建出字典。

    print(dd['new-key']) # []"""调用list()来建立一个列表。把这个新列表作为值,'new-key'作为它的键,放到dd中。返回这个列表的引用。"""print #defaultdict(<class 'list'>, {'dddd': []})注意如果在创建defaultdict的时候没有指定default_factory,查询不存在键会触发KeyError

    比如:

    特殊方法missing

    >>> data = [(1, 'a'), (2, 'b'), (3, 'c')]
    >>> data_dict = {num: letter for num, letter in data}
    >>> data_dict
    {1: 'a', 2: 'b', 3: 'c'}
    

    所有的映射类型找不到键的时候,都会使用到missing方法。 虽然基类dict并没有定义这个方法,但是dict知道有这么个东西的存在。 也就是说,如果有一个类继承了dict,然后这个继承类提供了missing方法, 那么在getitem碰到找不到键的时候,python会自动调用它,而不是抛出一个KeyError异常。 missing新葡亰496net:python数据结构字典,字典和聚合。方法只会被getitem调用

    常见的映射方法

    字典的变种

    下表为我们展示了 dict、defaultdict 和 OrderedDict 的常见方法(后两种是 dict 的变种,位于 collections模块内)。

    collections.OrderedDict:这个类型在添加键的时候会保持顺序,因此键的迭代次序总是一致的。

    新葡亰496net 5

    collections.ChainMap:该类型可以容纳数个不同的对象,然后在进行键查找操作的时候,这些对象会被当做一个整体逐个被查找。

    default_factory 并不是一个方法,而是一个可调用对象,它的值 defaultdict 初始化的时候由用户设定。 OrderedDict.popitem() 会移除字典最先插入的元素(先进先出);可选参数 last 如果值为真,则会移除最后插入的元素(后进先出)。用 setdefault 处理找不到的键

    import collections

    当字典 d[k] 不能找到正确的键的时候,Python 会抛出异常,平时我们都使用d.get(k, default) 来代替 d[k],给找不到的键一个默认值,还可以使用效率更高的 setdefault

    dict1 = {'a': 1, 'b': 2}dict2 = {'b': 3, 'c': 4}

    my_dict.setdefault(key, []).append(new_value)
    # 等同于
    if key not in my_dict:
     my_dict[key] = []
    my_dict[key].append(new_value)
    

    chain = collections.ChainMap(dict1, dict2)

    这两段代码的效果一样,只不过,后者至少要进行两次键查询,如果不存在,就是三次,而用 setdefault 只需一次就可以完成整个操作。

    print(chain.maps) # [{'b': 2, 'a': 1}, {'b': 3, 'c': 4}]

    那么,我们取值的时候,该如何处理找不到的键呢?

    print(list(chain.keys # ['b', 'c', 'a']

    映射的弹性查询

    print(list(chain.values # [2, 4, 1]

    有时候,就算某个键在映射里不存在,我们也希望在通过这个键读取值的时候能得到一个默认值。有两个途径能帮我们达到这个目的,一个是通过 defaultdict 这个类型而不是普通的 dict,另一个是给自己定义一个 dict 的子类,然后在子类中实现 __missing__ 方法。

    print(chain['b']) # 2print(chain.get # 2

    defaultdict:处理找不到的键的一个选择

    dict3 = {'f': 5}new_chain = chain.new_childprint(new_chain.maps) # [{'f': 5}, {'b': 2, 'a': 1}, {'b': 3, 'c': 4}]

    首先我们看下如何使用 defaultdict :

    reversed(new_chain.maps)print(new_chain.maps)collections.Counter:这个映射类型会给键准备一个整数计数器。每次更新一个键的时候都会增加这个计数器。

    import collections
    
    index = collections.defaultdict(list)
    index[new_key].append(new_value)
    

    collections.UserDict:把标准的dict用纯python又实现了一遍。

    这里我们新建了一个字典 index,如果键 new_key 在 index 中不存在,表达式 index[new_key] 会按以下步骤来操作:

    不可变的映射类型

    调用 list() 来建立一个新的列表把这个新列表作为值,'new_key' 作为它的键,放入 index 中返回这个列表的引用。

    标准库里所有的映射类型都是可变的,如果遇到不能让用户错误的修改某个映射。 使用types.MappingProxyType,如果给这个类一个映射,它会返回一个只读的映射视图。

    而这个用来生成默认值的可调用对象存放在名为 default_factory 的实例属性中。

    集合

    defaultdict 中的 default_factory 只会在 getitem 里调用,在其他方法中不会发生作用。比如 index[k] 这个表达式会调用 default_factory 创造的某个默认值,而 index.get(k) 则会返回 None。(这是因为特殊方法 missing 会在 defaultdict 遇到找不到的键的时候调用 default_factory,实际上,这个特性所有映射方法都可以支持)。

    相对dict,set这个概念在python算是比较年轻的,有set 跟 frozenset 集合的本质是许多唯一对象的聚集,所以集合中的元素必须都是可散列的,set类型本身是不可散列的,但是frozenset时可散列的。 集合可以进行中缀运算符。

    特殊方法 missing

    dict和set的背后

    所有映射在处理找不到的键的时候,都会牵扯到 missing 方法。但基类 dict 并没有提供 这个方法。不过,如果有一个类继承了 dict ,然后这个继承类提供了 missing 方法,那么在 getitem 碰到找不到键的时候,Python 会自动调用它,而不是抛出一个 KeyError 异常。

    python里的dict和set的效率有多高?为什么它们是无序的?为什么并不是所有的python对象都可以当做dict的键或者是set的元素?为什么dict的键和set元素的顺序是根据他们被添加的次序而定的,以及为什么在映射对象的生命周期中,这个顺序是一成不变的?为什么不应该在迭代循环dict或者是set的同时往里添加元素?字典中的散列表

    __missing__ 方法只会被 __getitem__ 调用。提供 missing 方法对 get 或者 __contains__(in 运算符会用到这个方法)这些方法的是有没有影响。

    散列表其实是一个稀疏数组(总是有空白元素的数组成为稀疏数组) 散列表里的单元通常叫做表元,在dict的散列表中每个键值对都占用一个表元,每个表元分都有两个部分,一个是对键的引用,一个是对值的引用。 如果把对象放到散列表,那么首先要计算这个元素键的散列值,python中可以用hash()方法来做这个事情。

    下面这段代码实现了 StrKeyDict0 类,StrKeyDict0 类在查询的时候把非字符串的键转化为字符串。

    散列值和相等性

    class StrKeyDict0(dict): # 继承 dict
     def __missing__(self, key):
     if isinstance(key, str):
      # 如果找不到的键本身就是字符串,抛出 KeyError 
      raise KeyError(key)
     # 如果找不到的键不是字符串,转化为字符串再找一次
     return self[str(key)]
     def get(self, key, default=None):
     # get 方法把查找工作用 self[key] 的形式委托给 __getitem__,这样在宣布查找失败钱,还能通过 __missing__ 再给键一个机会
     try:
      return self[key]
     except KeyError:
      # 如果抛出 KeyError 说明 __missing__ 也失败了,于是返回 default 
      return default
     def __contains__(self, key):
     # 先按传入的键查找,如果没有再把键转为字符串再找一次
     return key in self.keys() or str(key) in self.keys()
    

    如果1==1.0为真,那么`hash==hash也必须为真

    contains 方法存在是为了保持一致性,因为 k in d 这个操作会调用它,但我们从 dict 继承到的 contains 方法不会在找不到键的时候用 missing 方法。

    散列表算法

    my_dict.keys() 在 Python3 中返回值是一个 "视图","视图"就像是一个集合,而且和字典一样速度很快。但在 Python2中,my_dict.keys() 返回的是一个列表。 所以 k in my_dict.keys() 操作在 python3中速度很快,但在 python2 中,处理效率并不高。

    为了获取my_dict[search_key]背后的值,Python首先调用hash(search_key)来计算search_key的散列值,把这个值最低的几位数字当做偏移量,在散列表里查找表元(具体取几位,得看当前散列表的大小)。 若找到的表元为空的,则抛出KeyError异常。若不为空的,则表元里会有一对found_key:found_value。 这时候python会检验search_key == found_key是否为真,如果它们相等,就回返回found_value。 如果search_key和found_key不匹配的话,这种情况称为散列冲突。 发生原因是散列表所做的其实是把随机的元素映射到只有几位的数字上,而散列表本身的索引又只依赖于这个数字的一部分。 为了解决散列冲突,算法会在散列值中另外再取几位,然后用特殊方法处理一下,把新的到的数据在当做索引来寻找表元。

    如果要自定义一个映射类型,合适的策略是继承 collections.UserDict 类。这个类就是把标准 dict 用 python 又实现了一遍,UserDict 是让用户继承写子类的,改进后的代码如下:

    问题:如果定位一个表元?[^2]

    import collections
    
    class StrKeyDict(collections.UserDict):
    
     def __missing__(self, key):
     if isinstance(key, str):
      raise KeyError(key)
     return self[str(key)]
    
     def __contains__(self, key):
     # 这里可以放心假设所有已经存储的键都是字符串。因此只要在 self.data 上查询就好了
     return str(key) in self.data
    
     def __setitem__(self, key, item):
     # 这个方法会把所有的键都转化成字符串。
     self.data[str(key)] = item
    

    dict的实现及其导致的结果

    因为 UserDict 继承的是 MutableMapping,所以 StrKeyDict 里剩下的那些映射类型都是从 UserDict、MutableMapping 和 Mapping 这些超类继承而来的。

    散列表带给dict的优势和限制。

    Mapping 中提供了 get 方法,和我们在 StrKeyDict0 中定义的一样,所以我们在这里不需要定义 get 方法。

    键必须是可散列的

    字典的变种

    一个可散列的对象必须满足以下的需求: - 支持hash()函数,并且通过hash()方法所得到的散列值是不变的。 - 支持通过eq()方法来检测相等性。 - 若 a == b为真,则hash == hash也为真。

    在 collections 模块中,除了 defaultdict 之外还有其他的映射类型。

    用户自定义的对象默认是可散列的,它们的散列值有id()来获取。

    collections.OrderedDict collections.ChainMap collections.Counter 不可变的映射类型

    字典在内存上面开销巨大

    问题:标准库中所有的映射类型都是可变的,如果我们想给用户提供一个不可变的映射类型该如何处理呢?

    通常不需要优化,如果数据量巨大考虑使用tuple()来替代dict()

    从 Python3.3 开始 types 模块中引入了一个封装类名叫 MappingProxyType。如果给这个类一个映射,它会返回一个只读的映射视图(如果原映射做了改动,这个视图的结果页会相应的改变)。例如

    特殊方法slots

    >>> from types import MappingProxy Type
    >>> d = {1: 'A'}
    >>> d_proxy = MappingProxyType(d)
    >>> d_proxy
    mappingproxy({1: 'A'})
    >>> d_proxy[1]
    'A'
    >>> d_proxy[2] = 'x'
    Traceback(most recent call last):
     File "<stdin", line 1, in <module>
    TypeError: 'MappingProxy' object does not support item assignment
    >>> d[2] = 'B'
    >>> d_proxy[2] # d_proxy 是动态的,d 的改动会反馈到它上边
    'B'
    

    键查询很快

    字典中的散列表

    dict的实现是典型的空间换时间

    散列表其实是一个稀疏数组(总有空白元素的数组叫稀疏数组),在 dict 的散列表中,每个键值都占用一个表元,每个表元都有两个部分,一个是对键的引用,另一个是对值的引用。因为所有表元的大小一致,所以可以通过偏移量来读取某个表元。
    python 会设法保证大概有1/3 的表元是空的,所以在快要达到这个阈值的时候,原有的散列表会被复制到一个更大的空间。

    键的次序取决于添加顺序

    如果要把一个对象放入散列表,那么首先要计算这个元素的散列值。
    Python内置的 hash() 方法可以用于计算所有的内置类型对象。

    当往dict中添加新建而又发生散列冲突的时候,新建可能会被安排存放在另个一个位置。

    如果两个对象在比较的时候是相等的,那么它们的散列值也必须相等。例如 1==1.0 那么,hash(1) == hash(1.0)

    dict([key1,value1],[key2,value2]) == dict([key2,value2],[key1,value1]) # true虽然键的次序是乱的,但是被视作相等的。这就是为什么说字典是无序的原因。

    散列表算法

    往字典中添加新建可能会改变已有键的顺序

    为了获取 my_dict[search_key] 的值,Python 会首先调用 hash(search_key) 来计算 search_key 的散列值,把这个值的最低几位当做偏移量在散列表中查找元。若表元为空,抛出 KeyError 异常。若不为空,则表元会有一对 found_key:found_value。
    这时需要校验 search_key == found_key,如果相等,返回 found_value。
    如果不匹配(散列冲突),再在散列表中再取几位,然后处理一下,用处理后的结果当做索引再找表元。 然后重复上面的步骤。

    无论何时往字典添加新的键,Python的解释器都可能做出为字典扩容的决定。 扩容导致的结果是需要一个更大散列表,并把字典里已有的元素添加到新表里,这个过程就可能出现散列冲突,导致新的散列表中的键的次序变化。

    取值流程图如下:

    所以不要对字典同时迭代和修改。 python3中的.keys() .items() 和.values()方法返回都是字典的视图,这些方法返回的值更像是集合。

    新葡亰496net 6

    set的实现以及导致的结果可以参照没有值dict,python学习交流群 984632579 除了分享技术文章之外还有很多福利,加群领取学习资料可以领取包括不限于Python实战演练、PDF电子文档、面试集锦、学习资料等。

    添加新值和上述的流程基本一致,只不过对于前者,在发现空表元的时候会放入一个新元素,而对于后者,在找到相应表元后,原表里的值对象会被替换成新值。

    另外,在插入新值是,Python 可能会按照散列表的拥挤程度来决定是否重新分配内存为它扩容,如果增加了散列表的大小,那散列值所占的位数和用作索引的位数都会随之增加字典的优势和限制

    1、键必须是可散列的

    可散列对象要求如下:

    支持 hash 函数,并且通过__hash__() 方法所得的散列值不变支持通过 __eq__() 方法检测相等性若 a == b 为真, 则 hash(a) == hash(b) 也为真

    2、字典开销巨大

    因为字典使用了散列表,而散列表又必须是稀疏的,这导致它在空间上效率低下。

    3、键查询很快

    dict 的实现是典型的空间换时间:字典类型由着巨大的内存开销,但提供了无视数据量大小的快速访问。

    4、键的次序决定于添加顺序

    当往 dict 里添加新键而又发生散列冲突时,新建可能会被安排存放在另一个位置。

    5、往字典里添加新键可能会改变已有键的顺序

    无论何时向字典中添加新的键,Python 解释器都可能做出为字典扩容的决定。扩容导致的结果就是要新建一个更大的散列表,并把原有的键添加到新的散列表中,这个过程中可能会发生新的散列冲突,导致新散列表中次序发生变化。
    因此,不要对字典同时进行迭代和修改。

    您可能感兴趣的文章:

    • Python数据结构与算法之常见的分配排序法示例【桶排序与基数排序】
    • Python数据结构与算法之图的广度优先与深度优先搜索算法示例
    • Python数据结构与算法之使用队列解决小猫钓鱼问题
    • Python数据结构之栈、队列的实现代码分享
    • Python数据结构之顺序表的实现代码示例
    • Python数据结构与算法之列表(链表,linked list)简单实现
    • Python数据结构与算法之链表定义与用法实例详解【单链表、循环链表】
    • Python中顺序表的实现简单代码分享

    本文由新葡亰496net发布于奥门新萄京娱乐场,转载请注明出处:新葡亰496net:python数据结构字典,字典和聚合

    关键词:

上一篇:没有了

下一篇:没有了