加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Python > 正文

python – 在collections.defaultdict中禁止键添加

发布时间:2020-12-20 12:35:11 所属栏目:Python 来源:网络整理
导读:当在defaultdict对象中查询缺少的键时,该键会自动添加到字典中: from collections import defaultdictd = defaultdict(int)res = d[5]print(d)# defaultdict(class 'int',{5: 0})# we want this dictionary to remain empty 但是,我们通常只想在显式或隐式
当在defaultdict对象中查询缺少的键时,该键会自动添加到字典中:

from collections import defaultdict

d = defaultdict(int)
res = d[5]

print(d)
# defaultdict(<class 'int'>,{5: 0})
# we want this dictionary to remain empty

但是,我们通常只想在显式或隐式分配密钥时添加密钥:

d[8] = 1  # we want this key added
d[3] += 1 # we want this key added

一个用例是简单计数,以避免集合的较高开销.计数器,但这个功能一般也是可取的.

反例[原谅双关语]

这是我想要的功能:

from collections import Counter
c = Counter()
res = c[5]  # 0
print(c)  # Counter()

c[8] = 1  # key added successfully
c[3] += 1 # key added successfully

但Counter比defaultdict(int)明显慢.我发现性能下降通常比defaultdict(int)慢约2倍.

另外,显然Counter只能与defaultdict中的int参数相媲美,而defaultdict可以采用list,set等.

有没有办法有效地实现上述行为;例如,通过继承defaultdict?

基准测试示例

%timeit DwD(lst)           # 72 ms
%timeit dd(lst)            # 44 ms
%timeit counter_func(lst)  # 98 ms
%timeit af(lst)            # 72 ms

测试代码:

import numpy as np
from collections import defaultdict,Counter,UserDict

class DefaultDict(defaultdict):
    def get_and_forget(self,key):
        _sentinel = object()
        value = self.get(key,_sentinel)

        if value is _sentinel:
            return self.default_factory()
        return value

class DictWithDefaults(dict):
    __slots__ = ['_factory']  # avoid using extra memory

    def __init__(self,factory,*args,**kwargs):
        self._factory = factory
        super().__init__(*args,**kwargs)

    def __missing__(self,key):
        return self._factory()

lst = np.random.randint(0,10,100000)

def DwD(lst):
    d = DictWithDefaults(int)
    for i in lst:
        d[i] += 1
    return d

def dd(lst):
    d = defaultdict(int)
    for i in lst:
        d[i] += 1
    return d

def counter_func(lst):
    d = Counter()
    for i in lst:
        d[i] += 1
    return d

def af(lst):
    d = DefaultDict(int)
    for i in lst:
        d[i] += 1
    return d

关于赏金评论的注意事项:

自Bounty提供以来,@Aran-Fey’s solution已经更新,所以请忽略Bounty的评论.

解决方法

而不是搞乱 collections.defaultdict使它做我们想要的,似乎更容易实现我们自己:

class DefaultDict(dict):
    def __init__(self,default_factory,**kwargs):
        super().__init__(**kwargs)

        self.default_factory = default_factory

    def __getitem__(self,key):
        try:
            return super().__getitem__(key)
        except KeyError:
            return self.default_factory()

这可以按照您想要的方式工作:

d = DefaultDict(int)

res = d[5]
d[8] = 1 
d[3] += 1

print(d)  # {8: 1,3: 1}

但是,对于可变类型,它可能会出乎意料地表现:

d = DefaultDict(list)
d[5].append('foobar')

print(d)  # output: {}

这可能是defaultdict在访问不存在的密钥时记住该值的原因.

另一种选择是扩展defaultdict并添加一个新方法来查找值而不记住它:

from collections import defaultdict

class DefaultDict(defaultdict):
    def get_and_forget(self,key):
        return self.get(key,self.default_factory())

请注意,无论密钥是否已存在于dict中,get_and_forget方法每次都会调用default_factory().如果这是不合需要的,您可以使用sentinel值来实现它:

class DefaultDict(defaultdict):
    def get_and_forget(self,_sentinel)

        if value is _sentinel:
            return self.default_factory()
        return value

这样可以更好地支持可变类型,因为它允许您选择是否应该将值添加到dict中.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读