02-defaultdic类工作原理及应用实例(全网最全?含常值函数的创建)

本文介绍了Python中的defaultdict类,它是dict的扩展,用于处理未知映射关系,通过重写__missing__方法并接受可调用对象作为默认值生成器,使代码更加优雅且易于扩展。作者通过实例展示了defaultdict在处理动态键值对、计数、去重等场景中的应用。
摘要由CSDN通过智能技术生成

经常看到说“优雅地书写代码”,总是没啥感觉,直到这两天认真查阅了defaultdic这个类的一些资料。原来,灵活的应用,真的可以这么优雅、简单。


1  简要描述

defaultdic是collections包中的一个扩展类,是内置dict类的子类。

这个类比较简单,除了重写了dict类的__miss__方法,增加了一个实例变量外,其它与dict类一致。(但这个类的官方文档写得比较晦涩,感觉不用看那个文档,也就是这篇文章的第3部分)

运行流程如下:

1、当查询到不存在的key时,defaultdic对象调用__miss__(key)类,并将这个key与第1个参数的返回值进行映射绑定

2、使用的第1个实参(必须是可调用对象,可以是 int、str、list 等内置函数,也可以是自定义函数)的返回值进行映射绑定,形成[键:值]对,追加到实例中。

tips:如果在创建实例时,没有对第一个参数赋值,则defaultdic与内置dict类一样,遇到新的键,仍然会触发KeyError异常。

2  dict和defaultdic要解决什么样的问题

  • dict:更偏向于处理“已知映射关系”
  • defaultdic:偏向于处理“未知映射关系”

2.1  dict类的不足和defaultdic类的优势

有一些问题,虽然可以用映射关系来进行描述,但它们又无法穷举所有的可能(比如:各种问答系统),或者列举更多可能既浪费金钱,又不便操作(比如:生鲜店新菜和它的价格)。

当用dict来表述这类问题时,出现新的情况(新的键值),系统无法立即返回正确的值,对这个问题,需要额外花时间,单独进行处理,显得不那么聪明(优雅)。


而defaultdic类,刚好能够解决这种,“无法避免,而且一定会用类似的方法去处理”的问题。

它的实例对象在查询到不存在的键key时,按一定的规则,生成一个默认值,与这个key绑定,从而创建新的映射关系,再添加到类对象中去。

  • 比如:客户提出的一个新问题,可以自动建立一个回复,叫“待解决”,再把这个问题作为key,与“待解决”建立映射关系,装入问答库中,之后再对所有“待解决”的问题进行处理。
  • 再比如:蔬菜店上新品种,可以自动建立一个价格,赋予0值,再把这个品种和价格建立映射关系,装入产品库,之后再对所有价格为0的商品,修改价格后上架。

3  应用场景示例及代码

3.0  基本原理示例

defaultdict接收一个类对象或函数对象,在取值时,如果不存在对应的key则返回对应的函数返回值或默认构造函数的实例对象: 

3.0.1 以内置函数为参数

from collections import defaultdict

dic_1 = defaultdict(int)
dic_2 = defaultdict(tuple)
dic_3 = defaultdict(list)
dic_4 = defaultdict(str)
dic_5 = defaultdict(set)

这个地方default_factory的参数是int(),tuple()...,当查询key不存在时,自动将key与这些函数的默认返回值,进行绑定,再添加到对象的元素中。

from collections import defaultdict

dic_1 = defaultdict(int)
dic_2 = defaultdict(tuple)
dic_3 = defaultdict(list)
dic_4 = defaultdict(str)
dic_5 = defaultdict(set)

print(dic_1['1'])  # 0
print(dic_2['2'])  # ()
print(dic_3['3'])  # []
print(dic_4['4'])  # 没有显示,空字符串
print(dic_5['5'])  # set()

 3.0.2 以自定义函数或类为参数

当以自定义函数为参数时,返回值不能是None,否则defaultdict与dict没有区别;

当以类为参数时,__init__()需要有返回值,不能是None。

from collections import defaultdict


class SaySomething:
    def __init__(self, res='hello'):
        self.res = res

    def __str__(self):
        return self.res

    def getSomething(self, thing='!'):
        return self.res + thing


def someFun(res=123):
    return res


dicFrom_class = defaultdict(SaySomething)
dicFrom_fun = defaultdict(someFun)

print(dicFrom_class['1'])  # hello
print(dicFrom_fun['2'])  # 123

print(dicFrom_class['1'].getSomething(' world!')) # hello world!

以类为参数时,生成了类实例,可以使用下标访问内置方法,比如最后一条语句。

tips:这个地方点到为止,实际运用中,能够产生很多奇思妙想。

3.1 例程1:使用list作为参数,轻松建立一对多的字典

3.1.1  使用defaultdict类

from collections import defaultdict

s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d = defaultdict(list)
for k, v in s:
    d[k].append(v)

print(sorted(d.items()))
# 返回:[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

说明:

1、字典中的每个键key,首次出现时,还没有任何映射关系;

2、系统使用default_factory函数(此时是list),返回1个空列表

(此时,映射关系已形成,即:键与一个空列表);

3、然后,使用列表的append()方法,将值添加到空列表中,从而完成[键:值]映射;


4、 随后,当再次遇到相同键时,系统返回这个已有1个元素的列表。然后使用列表的append()方法,添加新的元素。

5、 这一操作方式与dict.setdefault()方法类似,但在运行效率上,更好一些。

3.1.2  使用dict.setdefault()方法,实现同样功能

s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d = {}
for k, v in s:
    d.setdefault(k, []).append(v)

print(list(d.items()))

# 返回:[('yellow', [1, 3]), ('blue', [2, 4]), ('red', [1])]

先不论效率问题,单从可读性上,使用defaultdict也会更好一些。

3.2  例程2:使用int作为参数,轻松计数

计算字符串中,某个字母的个数(真的是非常简单)

from collections import defaultdict

s = 'mississippi'
d = defaultdict(int)
for k in s:
    d[k] += 1

print(sorted(d.items()))

# 返回:[('i', 4), ('m', 1), ('p', 2), ('s', 4)]

3.3  例程3:利用集合特性,使用set作为参数,去除一对多关系中的重复值

from collections import defaultdict

s = [('red', 1), ('blue', 2), ('red', 3), ('blue', 4), ('red', 1), ('blue', 4)]
d = defaultdict(set)
for k, v in s:
    d[k].add(v)

print(sorted(d.items()))

#返回:[('blue', {2, 4}), ('red', {1, 3})]

3.4  例程4:使用lambda创建常值函数,按需生成缺省值

前面提到过,内置函数的常值如下:

int():0
tuple () :()
list(): []
str():'',空字符串
set():set(),空集合

而自定义函数的返回值也是固定的,记得吗?

defaultdic类的第一个参数,必须是可调用对象。

1、如果按一般的办法,无法向自定义函数传递参数;

到目前为止,我们创建defaultdic类对象的写法,都是:

d = defaultdic(fun),而不是d = defaultdic(fun())

2、无法传递参数,自然不可能在程序中,灵活改变缺省值

3、改变这一困境的办法就是使用lamda函数,让函数的返回值,也就是fun()跟fun一样,都是可调用对象。

from collections import defaultdict


def constant_factory(value):
    return lambda: value


d1 = defaultdict(constant_factory(12))
d2 = defaultdict(constant_factory('hello world'))

print(d1['1']) # 12
print(d2['str1']) # hello world

4  补充:官方文档(选择参考,可不看)

 官方文档链接:collections — Container datatypes — Python 3.12.2 documentation

4.1  定义

class collections.defaultdict(default_factory=None, /[, ...])

defaultdic类是内置字典类型的子类。实例化defaultdic类,返回一个类似字典的类型。

相对于dict类,它只是重写了一个函数,增加了一个可写实例变量,其它完全一致。

【tips:instance variable实例变量(只在实例对象中发生变化),相对于类变量class variable】


实例对象初始化时,第一个实参为default_factory属性提供初始值。默认情况下,它的值是None。其它参数,与普通的dict类一样,作为初始值,传递给类的构造函数(__init__),在实例化对象时使用。

4.2  重写的函数__missing__(key)

1、查询不存在的键值时,会调用这个函数。

2、这个函数与default_factory 属性值有关。

        初始化对象时:

        2.1 如果没有传入这个参数或参数值为None,就会触发KeyError异常;

        2.2 如果不为None,这个函数就会把查询的值作为【键】,并把default_factory的值作为【键值】,创建新的键值对,插入字典中。

也就是说,当使用defaultdic类初始化类对象时,如果没有传用一个可调用对象,作为第一个实参,那么它和普通的dict没有区别。

4.3  default_factory

实际是一个可调用对象,接收一个工厂函数作为参数, 可以是 int、str、list 等内置函数,也可以是自定义函数。工厂函数的返回值,就会传入__missing__(key)。

mark一下(需要后期加深):这个地方的用法非常灵活,包括了:类、类实例(实现了__call__方法)、lambda函数、生成器都可以作用这个地方的参数。这种灵活性,衍生出众多的应用场景。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值