Python字典(dict)使用技巧

字典dict是Python中使用频率很是高的数据结构,关于它的使用,也有许多的小技巧,掌握这些小技巧会让你高效地的使用dict,也会让你的代码更简洁.html

1.默认值python

假设name_for_userid存放的是name和id的映射关系:json

name_for_userid = {
    1: '张三',
    2: '李四',
    3: '王五',
}

获取name_for_userid中的某一个id的name,最简单的方式:数组

name_for_userid[1]
'张三'

这种方式虽然简单,但有一个不便之处就是,若是获取一个不存在于name_for_userid中的值程序会出现异常:数据结构

name_for_userid[4]
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-138-66c7bf5cd53a> in <module>()
----> 1 name_for_userid[4]

KeyError: 4

不少时候咱们不但愿程序出现这种异常,为了不这种状况,能够在获取name以前先判断该id是否已经存在name_for_userid中:app

if 4 in name_for_userid:
    print(name_for_userid[4])
else:
    print("Not Found")
"Not Found"

这种写法虽然可行,可是效率不高,由于获取某一个id对应的name最多须要查询两次:第一次是先判断该id是否存在name_for_userid中,若存在则第二次从name_for_userid中取出name值。还有一种写法:函数

try:
    print(name_for_userid[4])
except KeyError:
    print("Not Found")
"Not Found"

这种写法代码有些冗余,不够简洁,因此,dict提供了get方法,这个方法的好处就是能够设值default值,对于那些不存在于dict中的key会返回default值做为它的value:性能

name_for_userid.get(4,"None")
"None"

2.排序
ui

card = {'a': 4, 'c': 2, 'b': 3, 'd': 1}

用sorted对card排序,实际上是用card的key进行排序,以下:spa

sorted(card.items())
[('a', 4), ('b', 3), ('c', 2), ('d', 1)]

有些时候咱们须要对card的value进行排序,这个时候就可使用sorted函数中的key这个参数,咱们能够help一下这个函数的用法:

help(sorted)
Help on built-in function sorted in module builtins:
sorted(iterable, key
=None, reverse=False) Return a new list containing all items from the iterable in ascending order. A custom key function can be supplied to customise the sort order, and the reverse flag can be set to request the result in descending order.

因此咱们能够自定义一个key函数sorted_by_value:

def sorted_by_value(item):
    return item[1]

sorted(card.items(),key=sorted_by_value)
[('d', 1), ('c', 2), ('b', 3), ('a', 4)]

其实,若是使用lambda匿名函数的话,代码会更简洁:

sorted(card.items(),key=lambda item:item[1])

 若是让别人更轻松的理解你的意图,你能够尝试用下面的方法:

import operator
sorted(card.items(),key=operator.itemgetter(1))

须要注意的是,operator.itemgetter函数获取的不是值,而是定义了一个函数,经过该函数做用到对象上才能获取该对象上指定域的值。

其实,仍是比较喜欢lambda表达式,由于它灵活,简洁,好比,要对依据value的绝对值对dict排序,就能够这样写:

sorted(card.items(),key=lambda item:abs(item[1]))

3.switch...case

有句老话是这样说的,Python过去如今以及之后都不会有switch...case语句。这是由于if…elif…else和dict都能实现switch...case语句,因此switch...case就没有存在的必要了。

  • if...elif...else
if cond == 'cond_a':
    handle_a()
elif cond == 'cond_b':
    handle_b()
else:
    handle_default()

用if...elif...else来实现switch的功能,好处就是可读性强,可是若是处理的条件比较多,这样的写法就有些啰嗦和冗余。因此,这个时候dict就又闪亮登场了。

  • dict
func_dict = {
    'cond_a': handle_a,
    'cond_b': handle_b
}

cond = 'cond_a'
func_dict[cond]()

相对于if...elif...else,dict就显得清爽了许多,另外,若是想要实现default咱们也可使用dict的get()方法:

>>>func_dict.get(cond, handle_default)()

这样即便cond不在func_dict中,程序也不会异常停止。

下面再举一个例子,以下

>>>def dispatch_if(operator, x, y):
    if operator == 'add':
        return x + y
    elif operator == 'sub':
        return x - y
    elif operator == 'mul':
        return x * y
    elif operator == 'div':
        return x / y
>>>def dispatch_dict(operator, x, y):
    return {
        'add': lambda: x + y,
        'sub': lambda: x - y,
        'mul': lambda: x * y,
        'div': lambda: x / y,
    }.get(operator, lambda: None)()

若是想的再深刻点的话,上面的例子就性能上来讲不是最优的,由于,每次调用dispatch_dict函数的时候都会生成一个临时的包含各类操做码(加减乘除)的字典,最好的状况固然是这个字典只生成一次(常量),下次再调用此函数的时候,直接使用此字典就能够了,另外,python中的operator模块已经实现了加减乘除如:operator.mul, operator.div ,彻底能够替代lambda表达式。这里只是举个例子以更好的明白if…elif…else和dict的异同。

4.合并dict的几种方法

有的时候须要用一个dict去更新另外一个dict,好比,用户自定义的配置文件去覆盖默认配置,等等。假设有下面两个dict:

>>> xs = {'a': 1, 'b': 2}
>>> ys = {'b': 3, 'c': 4}

最经常使用的就是用dict内置的update()方法:

>>> zs = {}
>>> zs.update(xs)
>>> zs.update(ys)
>>> zs
{'a': 1, 'b': 3, 'c': 4}

还有一种方法就是使用内置的dict():

>>> zs = {**xs, **ys}
{'a': 1, 'b': 3, 'c': 4}

5.美观打印

当打印一些调试信息的时候,美观的打印输出有的时候能让人很直观的看出关键信息,提升调试的效率。

最朴素的打印:

>>> mapping = {'a': 23, 'b': 42, 'c': 0xc0ffee}
>>> str(mapping)
"{'c': 12648430, 'a': 23, 'b': 42}"

借助内置模块json能够实现更加直观的表现形式:

>>> import json
>>> json.dumps(mapping, indent=4, sort_keys=True)
{
    "a": 23,
    "b": 42,
    "c": 12648430
}

可是这种方法有必定的限制:

>>> mapping['d'] = {1, 2, 3}
>>> json.dumps(mapping)
TypeError: "set([1, 2, 3]) is not JSON serializable"

>>> json.dumps({all: 'yup'})
TypeError: "keys must be a string"

因此,还可使用下面的方式:

>>> import pprint
>>> pprint.pprint(mapping)
{'a': 23, 'b': 42, 'c': 12648430, 'd': set([1, 2, 3])}

6.彩蛋

 最后介绍几个关于dict比较有意思的东西 .

>>>print("keys:",list({True: 'yes', 1: 'no', 1.0: 'maybe'}.keys()))
>>>print("values:",list({True: 'yes', 1: 'no', 1.0: 'maybe'}.values()))

上面的代码会打印出什么结果呢,一开始我认为是下面的输出:

keys: [True,1,1.0]
values: ['yes','no','maybe']

真是too young too simple,其实结果应是:

keys: [True]
values: ['maybe']

why?再仔细想一想的化,既然key是True,那value为何不是yes而是maybe,再者,既然value是maybe,那key值为何不是1.0而是True呢?大家的疑问是否是和我同样?

其实,当Python在处理dict表达式的时候,它会先根据dict类建立出dict的实例,而后按照item(key:value)在dict表达式出现的顺序依次赋值,以下:

>>>xs = dict()
>>>xs[True] = 'yes'
>>>xs[1] = 'no'
>>>xs[1.0] = 'maybe'

更奇怪的是,dict会用下面的表达式认为他的key值都是相等的:

>>>True == 1 == 1.0
True

看到1==1.0你也许不会感到奇怪,可是为何会有True==1?当时看到这里我也有点懵逼的感受.

翻阅了一下Python的官方文档,在这一章节The standard type hierarchy中发现下面这段话:

“The Boolean type is a subtype of the integer type, and Boolean values behave like the values 0 and 1, respectively, in almost all contexts, the exception being that when converted to a string, the strings ‘False’ or ‘True’ are returned, respectively.”

意思就是说,Boolean是Int的子类,就是0和1.那么咱们能够验证一下:

>>>["No","Yes"][True]

结果果真是Yes,

>>>["No","Yes"][False]

结果也果真是No.可是为了清晰起见,不建议这样用.

看到这里你可能会问,这和dict有毛关系.接着往下看:

就Python而言,True,1和1.0都表示相同的字典key值,当解释器在执行dict表达式的时候,它会把后面的同一个key(True)的value值覆盖掉这个key(True)以前的value值.因此就会有下面的结果:

>>>{True: 'yes', 1: 'no', 1.0: 'maybe'}
{True: 'maybe'}

可是key为何没有被最后的1.0覆盖呢?其实道理也很简单,既然是同样的key,为何还要画蛇添足再用多余的时间去更新"相同的"key呢?这是出于CPython解释器性能的考虑.

>>>ys = {1.0: 'no'}
>>>ys[True] = 'yes'
>>>ys
输出:{1.0: 'yes'}

 根据咱们如今所了解到的,从表面上看是当key值相同时才会覆盖掉已有的value值,可是,事实证实,这不只仅和断定相等的__eq__有关系.

Python的字典是经过哈希表实现的,也就是说,它经过把key映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫作哈希函数,存放记录的数组叫作哈希表。可是没有十分完美的哈希函数计算出来的存储位置都是不同的,因此就会存在哈希冲突的状况,也就是不一样的key计算出来的存储位置是同一个地址.下面来看看到底是什么致使了更新value值.

  • 先定义以下的一个类:
>>>class AlwaysEquals:
    def __eq__(self, other):
        return True
    def __hash__(self):
        return id(self)
AlwaysEquals有两个方法:__eq____hash__.
首先,因为__eq__返回True,因此下面的条件表达式都是True:
>>>AlwaysEquals() == AlwaysEquals()
>>>AlwaysEquals() == 42
>>>AlwaysEquals() == 'waaat?'

其次,因为__hash__返回该对象的id值,也就是内存地址,因此有:

>>>objects = [AlwaysEquals(),AlwaysEquals(),AlwaysEquals()]
>>>[hash(obj) for obj in objects]
[140388524604608, 140388524604664, 140388524604720]

因此,综上,当值相等而hash值不相等时候,是否会存在value值覆盖:

>>>{AlwaysEquals(): 'yes', AlwaysEquals(): 'no'}

结果是:

{<__main__.AlwaysEquals at 0x7faec023bbe0>: 'yes',<__main__.AlwaysEquals at 0x7faec023bac8>: 'no'}
  • 从新定义一个类SameHash:
>>>class SameHash:
    def __hash__(self):
        return 1
>>>a = SameHash()
>>>b = SameHash()

因为__hash__返回1,因此有:

>>> a == b
False
>>> hash(a), hash(b)
(1, 1)

当值不相等而hash值相等的时候,是否会存在value值覆盖:

>>> {a: 'a', b: 'b'}
{ <SameHash instance at 0x7f7159020cb0>: 'a',<SameHash instance at 0x7f7159020cf8>: 'b' }

综上这两种状况,value值都不会被更新。

{True: 'yes', 1: 'no', 1.0: 'maybe'}  会出现更新key所对应的value值,是由于:

>>> True == 1 == 1.0
True
>>> (hash(True), hash(1), hash(1.0))
(1, 1, 1)

他们同时知足条件表达式相等且hash值也相等。

相关文章
相关标签/搜索