威尼斯人线上娱乐

泛映射类型,关于Python数据结构中字典的经验

23 4月 , 2019  

正文首要内容

可散列类型

泛映射类型

字典

    (一)字典推导式

  (2)管理不设有的键

    (三)字典的变种

集合

照耀的再斟酌

 

python高级——目录

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

 

python高端(3)—— 字典和集聚(泛映射类型),python映射

本篇首要介绍:常见的字典方法、如何管理查不到的键、标准库中 dict
类型的变种、散列表的行事规律等。一下是全体内容:

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

本篇首要介绍:常见的字典方法、怎么样管理查不到的键、标准库中 dict
类型的变种、散列表的劳作规律等。一下是全体内容:

泛映射类型

collections.abc 模块中有 Mapping 和 MutableMapping
那多个抽象基类,它们的作用是为 dict 和别的类似的类型定义方式接口。

威尼斯人线上娱乐 1

标准Curry全数映射类型都以采取 dict
来达成的,它们有个1块的限量,即唯有可散列的数据类型才具用做那么些映射里的键。

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

在 python
词汇表(

举个例子2个对象是可散列的,那么在这些目的的生命周期中,它的散列值是不改变的,而且以此目的需求实现
__hash__() 方法。此外可散列对象还要有 __eq__()
方法,那样本事跟其余键做比较。假使八个可散列对象是万分的,那么它们的散列只分明是如出1辙的

有趣的事那些定义,原子不可变类型(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。

字典推导

自 Python二.七以来,列表推导和生成器说明式的定义就移植到了字典上,从而有了字典推导。字典推导(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模块内)。

威尼斯人线上娱乐 2

default_factory 并不是3个办法,而是1个可调用对象,它的值 defaultdict
发轫化的时候由用户设定。 OrderedDict.popitem()
会移除字典起首插入的因素(先进先出);可选参数 last
假如值为真,则会移除最终插入的成分(后进先出)。用 setdefault
管理找不到的键

当字典 d[k] 无法找到科学的键的时候,Python
会抛出1贰分,平时大家都施用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)

这两段代码的成效等同,只不过,后者至少要进行两遍键查询,借使不存在,便是3回,而用
setdefault 只需2遍就可以实现全数操作。

那么,大家取值的时候,该怎么样管理找不到的键呢?

照耀的弹性查询

有时,纵然有些键在炫目里不存在,大家也可望在经过那个键读取值的时候能赢得二个暗许值。有五个门路能帮大家落成那一个目标,3个是经过
defaultdict 这么些体系而不是惯常的 dict,另3个是给自身定义一个 dict
的子类,然后在子类中得以落成 __missing__ 方法。

defaultdict:处理找不到的键的四个抉择

率先大家看下怎么样运用 defaultdict :

import collections

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

此处我们新建了2个字典 index,假诺键 new_key 在 index 中不存在,表明式
index[new_key] 会按以下步骤来操作:

调用 list() 来建立3个新的列表把那些新列表作为值,’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() 在 Python三 中重临值是3个”视图”,”视图”就好像3个汇聚,而且和字典一样速度非常的慢。但在
Python第22中学,my_dict.keys() 重临的是一个列表。 所以 k in my_dict.keys()
操作在 python叁中速度十分的快,但在 python2 中,管理功用并不高。

壹旦要自定义三个映射类型,合适的攻略是承接 collections.UserDict泛映射类型,关于Python数据结构中字典的经验。
类。那几个类正是把标准 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
不可变的映照类型

主题素材:规范库中颇具的投射类型都以可变的,假如大家想给用户提供2个不可变的映射类型该怎样管理呢?

从 Python3.叁 早先 types 模块中引入了3个封装类名字为
MappingProxyType。若是给那几个类一个炫人眼目,它会回去1个只读的粲焕视图(若是原映射做了改观,这一个视图的结果页会相应的更改)。举例

>>> 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
的散列表中,每一个键值都挤占二个表元,各样表元都有三个部分,3个是对键的引用,另一个是对值的引用。因为有着表元的大大小小同等,所以能够因而偏移量来读取有些表元。
python 会设法保险大致有1/三的表元是空的,所以在将在达到那个阈值的时候,原有的散列表会被复制到2个更加大的半空中。

假如要把一个目标放入散列表,那么首先要计算这些因素的散列值。
Python内置的 hash() 方法能够用于总计有所的嵌入类型对象。

一经多少个对象在相比较的时候是相等的,那么它们的散列值也非得相等。举例一==一.0 那么,hash(1) == hash(一.0)

散列表算法

为了获得 my_dict[search_key] 的值,Python 会首先调用
hash(search_key) 来计算 search_key
的散列值,把这一个值的最低四位当做偏移量在散列表中找找元。若表元为空,抛出
KeyError 格外。若不为空,则表元会有一些 found_key:found_value。
此刻要求校验 search_key == found_key,假如相等,重返 found_value。
假诺不包容(散列冲突),再在散列表中再取3位,然后管理一下,用管理后的结果作为索引再找表元。
然后再也上边的步子。

取值流程图如下:

威尼斯人线上娱乐 3

增加新值和上述的流水线基本一致,只可是对于前者,在意识空表元的时候会放入二个新因素,而对此后人,在找到呼应表元后,原表里的值对象会被替换到新值。

其它,在插入新值是,Python
大概会根据散列表的拥挤程度来支配是不是重新分配内部存款和储蓄器为它扩大体积,假如扩张了散列表的深浅,那散列值所占的位数和作为索引的位数都会跟着加多字典的优势和限制

壹、键必须是可散列的

可散列对象要求如下:

支撑 hash 函数,并且经过__hash__() 方法所得的散列值不改变协理通过
__eq__() 方法检查评定相等性若 a == b 为真, 则 hash(a) == hash(b) 也为真

2、字典开销巨大

因为字典使用了散列表,而散列表又不能够不是稀疏的,那致使它在上空上成效低下。

三、键查询急速

dict
的兑现是数壹数二的长空换时间:字典类型由着硬汉的内部存储器开销,但提供了无视数据量大小的快速访问。

4、键的主次决定于增多顺序

当往 dict
里增多新键而又发生散列争执时,新建或然会被布署存放在另一个岗位。

5、往字典里增加新键恐怕会变动已有键的次第

甭管哪一天向字典中增添新的键,Python
解释器都大概做出为字典扩大体积的主宰。扩容导致的结果正是要新建3个更加大的散列表,并把原来的键增加到新的散列表中,这一个进程中或者会时有发生新的散列顶牛,导致新散列表中次序产生变化。
于是,不要对字典同时拓展迭代和更动。

本篇主要介绍:常见的字典方法、怎么着管理查不到的键、规范库中 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上:

威尼斯人线上娱乐, 

泛映射类型

泛映射类型

'''
    泛映射类型就是广义上的对应关系,在数学中,我们将集合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}

(一)字典推导式

 

'''
    字典推导式:字典推导式的创建方法同列表推导式类似

    以下直接引用《流畅的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

 

威尼斯人线上娱乐 4

集合

'''
    集合对于很多人并不陌生,中学阶段就已经接触过。集合具有:
    (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>一书,如下三个表:

表一:集合的数学方法

 

表二:会集的可比运算

 

表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__...

 

正规Curry全数映射类型都是选取 dict
来促成的,它们有个体协会同的界定,即唯有可散列的数据类型技术用做那么些映射里的键。

辉映的再谈谈 

'''
    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》7柒页到80页对散列表算法以及字典、集结的功效、平时内需留意的难题张开了相比较详细的切磋,提议严刻并有趣味的同人阅读,该有的剧情对领悟字典类型无比有益,场景中捉摸不透的莫名其妙的bug大概会化解。

   主要的结论摘录如下:

  (一)键必须是可散列的

  (二)字典在内部存款和储蓄器上的花费巨大

  (三)键查询急速

  (4)键的顺序取决于增多顺序

  (伍)往字典里增添新键也许会变动已有键的1一

 

python高档体系小说目录

python高级——目录

 

 

 

字典和聚合(泛映射类型),python映射 本文主要内容 可散列类型 泛映射类型
字典 (一)字典推导式 (2)管理不设有…

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

python高端系列小说目录

python高级——目录

 

 

 

在 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

除此而外这一个措施以外,还是能够用字典推导的不2秘诀来修建新 dict。

字典推导

自 Python二.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模块内)。

威尼斯人线上娱乐 5

default_factory 并不是2个主意,而是1个可调用对象,它的值 defaultdict
伊始化的时候由用户设定。 OrderedDict.popitem()
会移除字典起始插入的要素(先进先出);可选参数 last
如果值为真,则会移除最终插入的成分(后进先出)。用 setdefault
管理找不到的键

当字典 d[k] 不可能找到科学的键的时候,Python
会抛出尤其,平日我们都选用d.get(k, default) 来代替
d[k],给找不到的键1个暗中认可值,还能动用频率更加高的 setdefault

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

这两段代码的作用同样,只可是,后者至少要开始展览两回键查询,即使不设有,就是三回,而用
setdefault 只需一次就足以做到全体操作。

那正是说,大家取值的时候,该怎么管理找不到的键呢?

炫人眼目的弹性查询

有时,尽管有个别键在炫酷里不设有,大家也期待在通过那些键读取值的时候能得到三个默许值。有多个路子能帮我们实现那一个目标,2个是通过
defaultdict 那些类型而不是一般的 dict,另三个是给和睦定义3个 dict
的子类,然后在子类中落到实处 __missing__ 方法。

defaultdict:管理找不到的键的八个选用

首先大家看下怎样运用 defaultdict :

import collections

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

此处大家新建了2个字典 index,假设键 new_key 在 index 中不存在,表达式
index[new_key] 会按以下步骤来操作:

调用 list() 来建立1个新的列表把这几个新列表作为值,’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 会自动调用它,而不是抛出3个 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 中重返值是贰个”视图”,”视图”就像二个集聚,而且和字典同样速度异常快。但在
Python第22中学,my_dict.keys() 重临的是多个列表。 所以 k in my_dict.keys()
操作在 python③中速度高速,但在 python二 中,处理功用并不高。

万一要自定义叁个辉映类型,合适的国策是承袭 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
不可变的照射类型

主题素材:标准库中具备的映照类型都以可变的,假诺大家想给用户提供一个不可变的炫丽类型该如何管理呢?

从 Python三.3 初始 types 模块中引进了一个封装类名字为
MappingProxyType。若是给那个类一个炫人眼目,它会回到2个只读的投射视图(借使原映射做了变动,那些视图的结果页会相应的转移)。比如

>>> 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'

字典中的散列表

散列表其实是1个疏散数组(总有空白成分的数组叫稀疏数组),在 dict
的散列表中,每一个键值都挤占多个表元,各种表元都有五个部分,3个是对键的引用,另三个是对值的引用。因为全体表元的分寸同等,所以能够通过偏移量来读取某些表元。
python 会设法保险大致有1/三的表元是空的,所以在就要达到那一个阈值的时候,原有的散列表会被复制到3个越来越大的空中。

假诺要把二个目的放入散列表,那么首先要总括那个因素的散列值。
Python内置的 hash() 方法能够用来总结有所的嵌入类型对象。

壹经八个目的在可比的时候是卓殊的,那么它们的散列值也无法不相等。举个例子1==壹.0 那么,hash(1) == hash(一.0)

散列表算法

为了获得 my_dict[search_key] 的值,Python 会首先调用
hash(search_key) 来计算 search_key
的散列值,把那个值的最低四个人当做偏移量在散列表中检索元。若表元为空,抛出
KeyError 分外。若不为空,则表元会有局部 found_key:found_value。
那时候急需校验 search_key == found_key,若是相等,重回 found_value。
若是不匹配(散列争执),再在散列表中再取2位,然后管理一下,用管理后的结果作为索引再找表元。
然后再一次上面包车型地铁手续。

取值流程图如下:

威尼斯人线上娱乐 6

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

除此以外,在插入新值是,Python
可能会遵守散列表的水泄不通程度来支配是还是不是重新分配内存为它扩大体量,若是扩充了散列表的高低,那散列值所占的位数和当作索引的位数都会跟着增多字典的优势和范围

一、键必须是可散列的

可散列对象供给如下:

匡助 hash 函数,并且经过__hash__() 方法所得的散列值不改变帮忙通过
__eq__() 方法检查实验相等性若 a == b 为真, 则 hash(a) == hash(b) 也为真

二、字典费用巨大

因为字典使用了散列表,而散列表又无法不是稀疏的,那造成它在半空中上功效低下。

三、键查询火速

dict
的得以实现是数壹数二的空中换时间:字典类型由着伟大的内部存款和储蓄器成本,但提供了无视数据量大小的快速访问。

肆、键的先后决定于加多顺序

当往 dict
里增添新键而又生出散列争论时,新建恐怕会被安顿存放在另1个职位。

伍、往字典里增加新键恐怕会退换已有键的逐壹

无论是曾几何时向字典中增添新的键,Python
解释器都或然做出为字典扩大体量的支配。扩大体积导致的结果正是要新建一个更加大的散列表,并把本来的键增加到新的散列表中,那么些进度中大概会生出新的散列争执,导致新散列表中次序爆发变化。
所以,不要对字典同时张开迭代和更改。

您恐怕感兴趣的稿子:

  • Python数据结构与算法之常见的分红排序法示例【桶排序与基数排序】
  • Python数据结构与算法之图的广度优先与深度优先找出算法示例
  • Python数据结构与算法之使用队列化解小猫钓鱼难点
  • Python数据结构之栈、队列的兑今世码分享
  • Python数据结构之顺序表的得以达成代码示例
  • Python数据结构与算法之列表(链表,linked
    list)轻松达成
  • Python数据结构与算法之链表定义与用法实例详解【单链表、循环链表】
  • Python中顺序表的落到实处轻易代码分享


相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图