'setdefault' 字典方法的用例

2025-02-17 09:24:00
admin
原创
78
摘要:问题描述:Python 2.5 中的添加大大减少了对 的方法collections.defaultdict的需求。这个问题是为了我们的集体教育:dict`setdefault`setdefault现在 Python 2.6/2.7 中什么仍然有用?的哪些流行用例setdefault已被取代collection...

问题描述:

Python 2.5 中的添加大大减少了对 的方法collections.defaultdict的需求。这个问题是为了我们的集体教育:dict`setdefault`

  1. setdefault现在 Python 2.6/2.7 中什么仍然有用?

  2. 的哪些流行用例setdefault已被取代collections.defaultdict


解决方案 1:

您可以说在填充字典之前defaultdict设置默认值很有用,并且对于填充字典时或之后设置默认值很有用。setdefault

可能是最常见的用例:对项目进行分组(在未分类的数据中,否则使用itertools.groupby

# really verbose
new = {}
for (key, value) in data:
    if key in new:
        new[key].append( value )
    else:
        new[key] = [value]


# easy with setdefault
new = {}
for (key, value) in data:
    group = new.setdefault(key, []) # key might exist already
    group.append( value )


# even simpler with defaultdict 
from collections import defaultdict
new = defaultdict(list)
for (key, value) in data:
    new[key].append( value ) # all keys have a default already

有时,您希望在创建字典后确保特定键存在。defaultdict在这种情况下不起作用,因为它仅在显式访问时创建键。假设您使用带有许多标头的 HTTP 类东西——其中一些是可选的,但您希望为它们设置默认值:

headers = parse_headers( msg ) # parse the message, get a dict
# now add all the optional headers
for headername, defaultvalue in optional_headers:
    headers.setdefault( headername, defaultvalue )

解决方案 2:

我通常使用setdefault关键字参数字典,例如在这个函数中:

def notify(self, level, *pargs, **kwargs):
    kwargs.setdefault("persist", level >= DANGER)
    self.__defcon.set(level, **kwargs)
    try:
        kwargs.setdefault("name", self.client.player_entity().name)
    except pytibia.PlayerEntityNotFound:
        pass
    return _notify(level, *pargs, **kwargs)

它非常适合调整采用关键字参数的函数包装器中的参数。

解决方案 3:

正如大多数答案所述setdefault,或者defaultdict可以让您在键不存在时设置默认值。但是,我想指出与用例有关的一个小警告setdefault。当 Python 解释器执行时,setdefault即使键存在于字典中,它也将始终评估函数的第二个参数。例如:

In: d = {1:5, 2:6}

In: d
Out: {1: 5, 2: 6}

In: d.setdefault(2, 0)
Out: 6

In: d.setdefault(2, print('test'))
test
Out: 6

如您所见,print即使字典中已经存在 2,也会执行 。如果您打算使用setdefault这样的优化,这一点就变得尤为重要memoization。如果将递归函数调用作为 的第二个参数添加setdefault,您将不会从中获得任何性能,因为 Python 总是会以递归方式调用该函数。

既然提到了记忆化,如果您考虑使用记忆化来增强函数,那么更好的选择是使用 functools.lru_cache 装饰器。lru_cache 可以更好地处理递归函数的缓存要求。

解决方案 4:

defaultdict当默认值是静态的时候(比如新列表),这很好,但如果它是动态的,那就不好了。

例如,我需要一个字典来将字符串映射到唯一的整数。defaultdict(int)将始终使用 0 作为默认值。同样,defaultdict(intGen())始终生成 1。

相反,我使用了常规字典:

nextID = intGen()
myDict = {}
for lots of complicated stuff:
    #stuff that generates unpredictable, possibly already seen str
    strID = myDict.setdefault(myStr, nextID())

请注意,这dict.get(key, nextID())是不够的,因为我以后也需要能够引用这些值。

intGen是我构建的一个小类,它自动增加一个 int 并返回其值:

class intGen:
    def __init__(self):
        self.i = 0

    def __call__(self):
        self.i += 1
    return self.i

如果有人有办法做到这一点,defaultdict我会很高兴看到它。

解决方案 5:

正如穆罕默德所说,有些情况下你只是偶尔希望设置默认值。一个很好的例子是先填充然后查询的数据结构。

考虑一个字典树。添加单词时,如果需要子节点但不存在,则必须创建该子节点来扩展字典树。查询单词是否存在时,缺少子节点表示该单词不存在,不应创建该子节点。

defaultdict 无法实现这一点。相反,必须使用具有 get 和 setdefault 方法的常规 dict。

解决方案 6:

setdefault()当我想要一个默认值时,我会使用它OrderedDict。没有一个标准的 Python 集合可以同时完成这两项功能,但是有方法 可以实现这样的集合。

解决方案 7:

从理论上讲,如果您有时setdefault想设置默认值,有时不想设置默认值,它仍然很方便。在现实生活中,我还没有遇到过这样的用例。

然而,标准库(Python 2.6,_threadinglocal.py)中出现了一个有趣的用例:

>>> mydata = local()
>>> mydata.__dict__
{'number': 42}
>>> mydata.__dict__.setdefault('widgets', [])
[]
>>> mydata.widgets
[]

我想说使用__dict__.setdefault是一个非常有用的案例。

编辑:碰巧,这是标准库中唯一的例子,并且它在注释中。所以可能不足以证明 的存在setdefault。不过,这里有一个解释:

对象将其属性存储在__dict__属性中。实际上,__dict__在对象创建后,属性随时可写。它也是一个字典,而不是defaultdict。一般情况下,对象拥有__dict__作为属性是不明智的,因为这会使每个对象都拥有所有合法标识符作为属性。因此,除了在被认为无用时将其完全删除外,defaultdict我无法预见 Python 对象会有任何变化来摆脱它。__dict__.setdefault

解决方案 8:

下面是一些 setdefault 的示例来展示它的实用性:

"""
d = {}
# To add a key->value pair, do the following:
d.setdefault(key, []).append(value)

# To retrieve a list of the values for a key
list_of_values = d[key]

# To remove a key->value pair is still easy, if
# you don't mind leaving empty lists behind when
# the last value for a given key is removed:
d[key].remove(value)

# Despite the empty lists, it's still possible to 
# test for the existance of values easily:
if d.has_key(key) and d[key]:
    pass # d has some values for key

# Note: Each value can exist multiple times!
"""
e = {}
print e
e.setdefault('Cars', []).append('Toyota')
print e
e.setdefault('Motorcycles', []).append('Yamaha')
print e
e.setdefault('Airplanes', []).append('Boeing')
print e
e.setdefault('Cars', []).append('Honda')
print e
e.setdefault('Cars', []).append('BMW')
print e
e.setdefault('Cars', []).append('Toyota')
print e

# NOTE: now e['Cars'] == ['Toyota', 'Honda', 'BMW', 'Toyota']
e['Cars'].remove('Toyota')
print e
# NOTE: it's still true that ('Toyota' in e['Cars'])

解决方案 9:

我重写了已接受的答案,并使其更方便新手理解。

#break it down and understand it intuitively.
new = {}
for (key, value) in data:
    if key not in new:
        new[key] = [] # this is core of setdefault equals to new.setdefault(key, [])
        new[key].append(value)
    else:
        new[key].append(value)


# easy with setdefault
new = {}
for (key, value) in data:
    group = new.setdefault(key, []) # it is new[key] = []
    group.append(value)



# even simpler with defaultdict
new = defaultdict(list)
for (key, value) in data:
    new[key].append(value) # all keys have a default value of empty list []

另外,我将这些方法归类以供参考:

dict_methods_11 = {
            'views':['keys', 'values', 'items'],
            'add':['update','setdefault'],
            'remove':['pop', 'popitem','clear'],
            'retrieve':['get',],
            'copy':['copy','fromkeys'],}

解决方案 10:

defaultdict( )的一个缺点是,每次给定不存在的键(例如,),对象都会创建一个新项目。此外,dict类通常比类少见得多,IME 更难序列化它。dict.setdefault`defaultdict==printdefaultdictdict`

PS IMO 函数|方法不是为了改变对象,就不应该改变对象。

解决方案 11:

我经常使用 setdefault 在字典中设置默认值(!!!);最常见的是 os.environ 字典:

# Set the venv dir if it isn't already overridden:
os.environ.setdefault('VENV_DIR', '/my/default/path')

不太简洁地说,它看起来像这样:

# Set the venv dir if it isn't already overridden:
if 'VENV_DIR' not in os.environ:
    os.environ['VENV_DIR'] = '/my/default/path')

值得注意的是,您还可以使用结果变量:

venv_dir = os.environ.setdefault('VENV_DIR', '/my/default/path')

但这比在 defaultdicts 存在之前就没有那么必要了。

解决方案 12:

另一个我认为上面没有提到的用例。有时,你会根据对象的 ID 保存一个缓存字典,其中主实例位于缓存中,并且你想在丢失时设置缓存。

return self.objects_by_id.setdefault(obj.id, obj)

如果您每次都希望为每个不同的 ID 保留一个实例,而不管您每次如何获取对象,那么这种方法就很有用。例如,当对象属性在内存中更新并且保存到存储中被推迟时。

解决方案 13:

我刚刚偶然发现一个非常重要的用例: dict.setdefault()当您只想要一个规范对象(而不是恰好相等的多个对象)时,它非常适合多线程代码。

例如,(Int)FlagPython 3.6.0 中的 Enum 有一个错误:如果多个线程竞争一个复合(Int)Flag成员,则最终可能会出现多个:

from enum import IntFlag, auto
import threading

class TestFlag(IntFlag):
    one = auto()
    two = auto()
    three = auto()
    four = auto()
    five = auto()
    six = auto()
    seven = auto()
    eight = auto()

    def __eq__(self, other):
        return self is other

    def __hash__(self):
        return hash(self.value)

seen = set()

class cycle_enum(threading.Thread):
    def run(self):
        for i in range(256):
            seen.add(TestFlag(i))

threads = []
for i in range(8):
    threads.append(cycle_enum())

for t in threads:
    t.start()

for t in threads:
    t.join()

len(seen)
# 272  (should be 256)

解决方案是将其setdefault()作为保存计算的复合成员的最后一步 - 如果已经保存了另一个,那么将使用它而不是新的,以保证唯一的枚举成员。

解决方案 14:

除了建议的内容之外,setdefault在您不想修改已设置的值的情况下,这可能会很有用。例如,当您有重复的数字并且想要将它们视为一个组时。在这种情况下,如果您遇到duplicate已设置的重复键,则不会更新该键的值。您将保留第一个遇到的值。就好像您只迭代/更新重复的键一次一样。

以下是记录排序列表的键/元素索引的代码示例:

nums = [2,2,2,2,2]
d = {}
for idx, num in enumerate(sorted(nums)):
    # This will be updated with the value/index of the of the last repeated key
    # d[num] = idx # Result (sorted_indices): [4, 4, 4, 4, 4]
    # In the case of setdefault, all encountered repeated keys won't update the key.
    # However, only the first encountered key's index will be set 
    d.setdefault(num,idx) # Result (sorted_indices): [0, 0, 0, 0, 0]

sorted_indices = [d[i] for i in nums]

解决方案 15:

[编辑]大错特错! setdefault 总是会触发 long_computation,因为 Python 很急切。

扩展 Tuttle 的答案。对我来说,最好的用例是缓存机制。而不是:

if x not in memo:
   memo[x]=long_computation(x)
return memo[x]

它占用 3 行代码和 2 或 3 次查找,我很乐意写入:

return memo.setdefault(x, long_computation(x))

解决方案 16:

我喜欢这里给出的答案:

http://stupidpythonideas.blogspot.com/2013/08/defaultdict-vs-setdefault.html

简而言之,(在非性能关键型应用程序中)应根据如何处理下游空键查找( KeyError与默认值)来做出决定。

解决方案 17:

的不同用例setdefault()当您不想覆盖已设置的键的值时。defaultdict覆盖,而setdefault()不覆盖。 对于嵌套字典,更常见的情况是,只有在尚未设置键时才希望设置默认值,因为您不想删除当前的子字典。 这时您可以使用setdefault()

例如defaultdict

>>> from collection import defaultdict()
>>> foo = defaultdict()
>>> foo['a'] = 4
>>> foo['a'] = 2
>>> print(foo)
defaultdict(None, {'a': 2})

setdefault不会覆盖:

>>> bar = dict()
>>> bar.setdefault('a', 4)
>>> bar.setdefault('a', 2)
>>> print(bar)
{'a': 4}

解决方案 18:

CPython 中的另一个用例setdefault是它在所有情况下都是原子的,而defaultdict如果使用从 lambda 创建的默认值,它将不是原子的。

cache = {}

def get_user_roles(user_id):
    if user_id in cache:
        return cache[user_id]['roles']

    cache.setdefault(user_id, {'lock': threading.Lock()})

    with cache[user_id]['lock']:
        roles = query_roles_from_database(user_id)
        cache[user_id]['roles'] = roles

如果两个线程cache.setdefault同时执行,则只有其中一个线程能够创建默认值。

如果你使用 defaultdict:

cache = defaultdict(lambda: {'lock': threading.Lock()}

这会导致竞争条件。在我上面的例子中,第一个线程可以创建一个默认锁,第二个线程可以创建另一个默认锁,然后每个线程都可以锁定自己的默认锁,而不是每个线程都尝试锁定单个锁的期望结果。


从概念上讲,setdefault基本上表现得像这样(如果使用空列表、空字典、int 或其他不是用户 Python 代码(如 lambda)的默认值,defaultdict 也会表现得像这样):

gil = threading.Lock()

def setdefault(dict, key, value_func):
    with gil:
        if key not in dict:
            return
       
        value = value_func()

        dict[key] = value

从概念上讲,defaultdict基本上表现得像这样(仅在使用像 lambda 这样的 python 代码时 - 如果使用空列表则不成立):

gil = threading.Lock()

def __setitem__(dict, key, value_func):
    with gil:
        if key not in dict:
            return

    value = value_func()

    with gil:
        dict[key] = value
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2098  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1460  
  建筑行业正处于数字化转型的关键时期,建筑产品生命周期管理(PLM)系统的实施对于提升项目效率、质量和协同性至关重要。特别是在 2025 年,基于建筑信息模型(BIM)的项目进度优化工具成为众多建筑企业关注的焦点。这些工具不仅能够整合项目全生命周期的数据,还能通过精准的分析和模拟,为项目进度管理提供强大支持。BIM 与建...
plm是什么软件   13  
  PLM系统开发的重要性与现状PLM(产品生命周期管理)系统在现代企业的产品研发、生产与管理过程中扮演着至关重要的角色。它贯穿产品从概念设计到退役的整个生命周期,整合了产品数据、流程以及人员等多方面的资源,极大地提高了企业的协同效率和创新能力。通过PLM系统,企业能够实现产品信息的集中管理与共享,不同部门之间可以实时获取...
国产plm软件   15  
  PLM(产品生命周期管理)系统在企业产品研发与管理过程中扮演着至关重要的角色。随着市场竞争的加剧和技术的飞速发展,企业对PLM系统的迭代周期优化需求日益迫切。2025年敏捷认证对项目管理提出了新的要求,其中燃尽图作为一种强大的可视化工具,在PLM系统迭代周期优化中有着广泛且重要的应用。深入探讨这些应用,对于提升企业的项...
plm系统主要干什么的   16  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用