【问题标题】:Is there a dictionary-like object that is immutable? [duplicate]是否存在不可变的类字典对象? [复制]
【发布时间】:2023-03-28 01:19:01
【问题描述】:

我想要一个 Python 对象,它可以灵活地获取任何键,并且我可以像字典一样通过键访问,但它是不可变的。一种选择可能是灵活地生成namedtuple,但这样做是不好的做法吗?在下面的示例中,例如,linter 不会期望 nt 具有属性 a

例子:

from collections import namedtuple

def foo(bar):
    MyNamedTuple = namedtuple("MyNamedTuple", [k for k in bar.keys()])
    d = {k: v for k, v in bar.items()}
    return MyNamedTuple(**d)

>>> nt = foo({"a": 1, "b": 2})

【问题讨论】:

    标签: python collections namedtuple


    【解决方案1】:

    我在 cmets 中提到过,我不确定为什么需要这样做。
    但是可以简单地覆盖dictionary class__setitem__。尽管如此,这可能(很可能)会导致问题发生。一个最小的例子是:

    class autodict(dict):
        def __init__(self, *args, **kwargs):
            super(autodict, self).__init__(*args, **kwargs)
    
        def __getitem__(self, key):
            val = dict.__getitem__(self, key)
            return val
    
        def __setitem__(self, key, val):
            pass
    
    x = autodict({'a' : 1, 'b' : 2})
    x['c'] = 3
    print(x)
    

    这将产生{'a': 1, 'b': 2},从而忽略x['c'] = 3 集。


    一些好处

    与命名元组相比,使用字典继承的速度差异在 40-1000 倍之间。 (粗略的速度测试见下文)

    in 运算符适用于字典,但像这样使用命名元组时效果不佳:

    'a' in nt == False
    'a' in x == True
    

    您可以使用键访问字典样式代替 (因为没有更好的术语) JavaScript 样式

    x['a'] == nt.a
    

    虽然这是一个品味问题。

    您也不必对键挑剔,因为字典基本上支持任何键标识符:

    x[1] = 'a number'
    nt = foo({1 : 'a number'})
    

    命名元组将产生Type names and field names must be valid identifiers: '1'


    优化(定时)

    现在,这是一个粗略的例子,它会根据系统、月亮在天空中的位置等而有很大差异。但作为一个粗略的例子:

    import time
    from collections import namedtuple
    
    class autodict(dict):
        def __init__(self, *args, **kwargs):
            super(autodict, self).__init__(*args, **kwargs)
            #self.update(*args, **kwargs)
    
        def __getitem__(self, key):
            val = dict.__getitem__(self, key)
            return val
    
        def __setitem__(self, key, val):
            pass
    
        def __type__(self, *args, **kwargs):
            return dict
    
    def foo(bar):
        MyNamedTuple = namedtuple("MyNamedTuple", [k for k in bar.keys()])
        d = {k: v for k, v in bar.items()}
        return MyNamedTuple(**d)
    
    start = time.time()
    for i in range(1000000):
        nt = foo({'x'+str(i) : i})
    end = time.time()
    print('Named tuples:', end - start,'seconds.')
    
    start = time.time()
    for i in range(1000000):
        x = autodict({'x'+str(i) : i})
    end = time.time()
    print('Autodict:', end - start,'seconds.')
    

    结果:

    Named tuples: 59.21987843513489 seconds.
    Autodict: 1.4844810962677002 seconds.
    

    字典设置在我的书中,快得离谱。尽管这很可能与命名元组设置中的多个 for 循环有关,但可以通过某种方式轻松解决。但是对于基本的理解,这是一个很大的区别。该示例显然没有测试更大的一次性创建或访问时间。只是,“如果你使用这些选项在一段时间内创建数据集,你会浪费多少时间”:)

    奖励:如果您有一个大型基础词典,并且想要冻结它怎么办?

    base_dict = {'x'+str(i) : i for i in range(1000000)}
    
    start = time.time()
    nt = foo(base_dict)
    end = time.time()
    print('Named tuples:', end - start,'seconds.')
    
    start = time.time()
    x = autodict(base_dict)
    end = time.time()
    print('Autodict:', end - start,'seconds.')
    

    嗯,差异比我预期的要大。x1038.5 快了几倍。
    (我将 CPU 用于其他事情,但我认为这是公平的游戏) p>

    Named tuples: 154.0662612915039 seconds.
    Autodict: 0.1483476161956787 seconds.
    

    【讨论】:

    • 感谢@Torxed!虽然这与上面的 foo 示例相比有什么优势?
    • @JamieEdgecombe 除了你可以定义custom metaclass references 并让应用程序误以为它是一个实际的字典而不是<class autodict> 之外,我能想到的并不多——尽管在大多数情况下这毫无意义。
    • 只是为了咯咯笑,我也会在这个和命名元组之间进行一些速度比较,尽管我认为它们应该是相同的。
    • 另一个优势,我猜,许多用例是您可以添加一个尚未存在的密钥到您的 x 但不存在 nt
    • 冻结字典的优点是您可以将它与@cache 装饰器一起使用,这是一种记忆函数的“转到”方式。
    【解决方案2】:

    您可以使用frozenset() 创建一个最小类来存储数据,然后添加自定义__getitem__() 方法。

    class Idict:
        def __init__(self, d):
            self.d = frozenset(d.items())
    
        def __getitem__(self, k):
            return [v for _k,v in self.d if _k == k][0]
    
    
    d = {'a':1, 'b':2}
    a = Idict(d)
    a['a'] #1
    a['h'] = 0 #TypeError: 'Idict' object does not support item assignment
    

    【讨论】:

    • 这样做的问题是它以丢失字典的O(1) 查找为代价来实现不变性。
    • @JohnColeman 是的,这是真的。这不是一种有效的实现方式。
    • 另一方面,我能想到的冻结字典的唯一自然用例是用于标记数据的小数据包,可能传递给kwargs,在这种情况下,渐近不是那么相关.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-29
    • 1970-01-01
    • 1970-01-01
    • 2019-09-05
    • 1970-01-01
    • 1970-01-01
    • 2018-04-17
    相关资源
    最近更新 更多