python小技巧-1

在阅读《流畅的python》以及《深刻理解python特性》时发现这两本书都说起了python的一个库:collection.namedtuple,这个库能够快速的建立一个tuple对象而且能够为其命令,并且输出查看该对象值时,tuple中是以key=value的形式存储,方便了用户的使用,最主要的是:这个对象在内存中所消耗的字节数与普通tuple同样!python

>>> from collections import namedtuple
>>> Card = namedtuple('Card',"rank suit")
>>> Card
<class '__main__.Card'>
>>> type(Card)
<class 'type'>
>>> ranks = [str(n) for n in range(2,11)] +list("JQKA")
>>> suits = 'spades diamonds clubs hearts'
>>> cards = [Card(rank,suit) for rank in ranks for suit in suits]
>>> cards
[Card(rank='2', suit='s'), Card(rank='2', suit='p'), Card(rank='2', suit='a'), Card(rank='2', suit='d'), Card(rank='2', suit='e'), Card(rank='2', suit='s'), Card(rank='2', suit=' ') 
 后面省略

能够发现使用了namedtupe后,每一个tuple有了名称,并且存储格式为key=value的形式。算法

同时快速建立牌组的列表的推到式使用于笛卡尔积这样的形式,快速的构建list,不管是一个变量,仍是变量;数组

关于tuple数据结构

都知道tuple中存储的是不能轻易修改的变量值,那么下面这段代码修改后会发现什么状况呢?函数

>>> t = (1,2,[30,40])
>>> t[2]
[30, 40]
>>> t[2]+=[50,60]

状况1:因为tuple存储的是不可修改的变量,修改会发生错误;学习

状况2:因为list是可变对象,能够修改为功;并且没有什么错误提示;优化

惋惜的是,修改后既提示了错误,也让用户修改了其中可变对象的值ui

>>> t = (1,2,[30,40])
>>> t[2]
[30, 40]
>>> t[2]+=[50,60]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])

关于list spa

最近学习list的时候发现一个有趣的现象,那就是我切片的时候,切片范围比赋予的值多时,发现竟然没有赋值的那一块部分的值竟然消失了,留下代码记录一下:code

>>> a = [1,2,3,4,5,6,7]
>>> a[2:5]=[33,44]
>>> a
[1, 2, 33, 44, 6, 7]

能够说切片功能十分强大,能够对序列进行嫁接、切除或就地修改操做。

关于dict

dict能够说是python中最强大的数据结构,由于他能够很方便的经过key值查到value;可是它花费的代价倒是很大的,相比于list来讲;

>>> import sys
>>> a = set()
>>> b = {}
>>> sys.getsizeof(a)
232
>>> sys.getsizeof(b)
248
>>> c = []
>>> sys.getsizeof(c)
72
>>> d = ()
>>> sys.getsizeof(d)
56

能够发现一个字典要消耗248字节,集合要消耗232字节,list只须要72字节,而tuple只须要花费56字节!

关于dict的底层实现,下边是书中的话语,已经解释的十分详细,无需我再添油加醋;

散列表实际上是一个稀疏数组(老是有空白元素的数组称为稀疏数组)。 在通常的数据结构教材中,散列表里的单元一般叫做表元(bucket)。 在 dict 的散列表当中,每一个键值对都占用一个表元,每一个表元都有两 个部分,一个是对键的引用,另外一个是对值的引用。由于全部表元的大 小一致,因此能够经过偏移量来读取某个表元。

由于 Python 会设法保证大概还有三分之一的表元是空的,因此在快要达 到这个阈值的时候,原有的散列表会被复制到一个更大的空间里面。

若是要把一个对象放入散列表,那么首先要计算这个元素键的散列值。 Python 中能够用 hash() 方法来作这件事情,接下来会介绍这一点。

  1. 散列值和相等性

    内置的 hash() 方法能够用于全部的内置类型对象。若是是自定义 对象调用 hash() 的话,实际上运行的是自定义的 __hash__。如 果两个对象在比较的时候是相等的,那它们的散列值必须相等,否 则散列表就不能正常运行了。例如,若是 1 == 1.0 为真,那么 hash(1) == hash(1.0) 也必须为真,但其实这两个数字(整型 和浮点)的内部结构是彻底不同的。

    为了让散列值可以胜任散列表索引这一角色,它们必须在索引空间 中尽可能分散开来。这意味着在最理想的情况下,越是类似但不相等 的对象,它们散列值的差异应该越大。

  2. 散列表算法

    为了获取 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 不匹配的话,这种状况称为散列 冲突。发生这种状况是由于,散列表所作的实际上是把随机的元素映射到只有几位的数字上,而散列表自己的索引又只依赖于这个数字 的一部分。为了解决散列冲突,算法会在散列值中另外再取几位, 而后用特殊的方法处理一下,把新获得的数字再看成索引来寻找表 元。若此次找到的表元是空的,则一样抛出 KeyError;若非 空,或者键匹配,则返回这个值;或者又发现了散列冲突,则重复 以上的步骤。

​ 图 3-3 展现了这个算法的示意图。

hash

3-3:从字典中取值的算法流程图;给定一个键,这个算法要 么返回一个值,要么抛出 KeyError 异常

添加新元素和更新现有键值的操做几乎跟上面同样。只不过对于前者,在发现空表元的时候会放入一个新元素;对于后者,在找到相对应的表元后,原表里的值对象会被替换成新值。

另外在插入新值时,Python 可能会按照散列表的拥挤程度来决定是 否要从新分配内存为它扩容。若是增长了散列表的大小,那散列值所占的位数和用做索引的位数都会随之增长,这样作的目的是为了 减小发生散列冲突的几率。

表面上看,这个算法彷佛很费事,而实际上就算 dict 里有数百万 个元素,多数的搜索过程当中并不会有冲突发生,平均下来每次搜索 可能会有一到两次冲突。在正常状况下,就算是最不走运的键所遇 到的冲突的次数用一只手也能数过来。

了解 dict 的工做原理能让咱们知道它的所长和所短,以及从它衍 生而来的数据类型的优缺点。下面就来看看 dict 这些特色背后的 缘由。

dict 的实现及其致使的结果

  1. 键必须是可散列的 一个可散列的对象必须知足如下要求。

    (1) 支持 hash() 函数,而且经过 __hash__() 方法所获得的散列值是不变的。

    (2) 支持经过 __eq__() 方法来检测相等性。
    (3) 若 a == b 为真,则 hash(a) == hash(b) 也为真。

    全部由用户自定义的对象默认都是可散列的,由于它们的散列值由 id() 来获取,并且它们都是不相等的。

    若是你实现了一个类的 eq 方法,而且但愿它是可 散列的,那么它必定要有个恰当的 hash 方法,保证在 a == b 为真的状况下 hash(a) == hash(b) 也一定为真。不然 就会破坏恒定的散列表算法,致使由这些对象所组成的字典和 集合彻底失去可靠性,这个后果是很是可怕的。另外一方面,如 果一个含有自定义的 eq 依赖的类处于可变的状态,那就不要在这个类中实现 hash 方法,由于它的实例是不可散列的。

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

    因为字典使用了散列表,而散列表又必须是稀疏的,这致使它在空间上的效率低下。举例而言,若是你须要存放数量巨大的记录,那 么放在由元组或是具名元组构成的列表中会是比较好的选择;最好

​ 不要根据 JSON 的风格,用由字典组成的列表来存放这些记录。用元组取代字典就能节省空间的缘由有两个:其一是避免了散列表所 耗费的空间,其二是无需把记录中字段的名字在每一个元素里都存一 遍。

​ 在用户自定义的类型中,__slots__ 属性能够改变实例属性的存储 方式,由 dict 变成 tuple,相关细节在 9.8 节会谈到。

​ 记住咱们如今讨论的是空间优化。若是你手头有几百万个对象,而 你的机器有几个 GB 的内存,那么空间的优化工做能够等到真正需 要的时候再开始计划,由于优化每每是可维护性的对立面。

3.键查询很快

dict 的实现是典型的空间换时间:字典类型有着巨大的内存开 销,但它们提供了无视数据量大小的快速访问——只要字典能被装在内存里。

4.键的次序取决于添加顺序

当往 dict 里添加新键而又发生散列冲突的时候,新键可能会被安 排存放到另外一个位置。因而下面这种状况就会发生:由 dict([key1, value1), (key2, value2)] 和 dict([key2, value2], [key1, value1]) 获得的两个字典,在进行比较的时 候,它们是相等的;可是若是在 key1 和 key2 被添加到字典里的过程当中有冲突发生的话,这两个键出如今字典里的顺序是不同 的。

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

    不管什么时候往字典里添加新的键,Python 解释器均可能作出为字典扩 容的决定。扩容致使的结果就是要新建一个更大的散列表,并把字 典里已有的元素添加到新表里。这个过程当中可能会发生新的散列冲 突,致使新散列表中键的次序变化。要注意的是,上面提到的这些 变化是否会发生以及如何发生,都依赖于字典背后的具体实现,因 此你不能很自信地说本身知道背后发生了什么。若是你在迭代一个 字典的全部键的过程当中同时对字典进行修改,那么这个循环颇有可 能会跳过一些键——甚至是跳过那些字典中已经有的键。

    由此可知,不要对字典同时进行迭代和修改。若是想扫描并修改一 个字典,最好分红两步来进行:首先对字典迭代,以得出须要添加 的内容,把这些内容放在一个新字典里;迭代结束以后再对原有字 典进行更新。

这篇博客介绍python3.6之后的字典实现十分详细

https://zhuanlan.zhihu.com/p/...

Python3.6以后,往字典里添加新键是有序的,不存在改变已有键顺序的状况了

同理,对于集合来讲,也是异常消耗内存的;集合有以下特色:

1:集合里的元素必须是可散列的。

2:集合很消耗内存。

3:能够很高效地判断元素是否存在于某个集合。

4:元素的次序取决于被添加到集合里的次序。

5:往集合里添加元素,可能会改变集合里已有元素的次序。

相关文章
相关标签/搜索