是否存在不可变的类似字典的对象?
Is there a dictionary-like object that is immutable?
我想要一个 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})
我在评论中提到过,我不确定为什么需要这样做。
但是可以简单地覆盖 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
循环有关,并且可能可以通过某种方式轻松补救。但是对于基本的理解,这是一个很大的不同。该示例显然不会测试更大的一次性创建或访问时间。只是,"what if you use these options to create data-sets over a period of time, how much time would you loose" :)
奖金:如果你有一个很大的基础词典,并且想冻结它怎么办?
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 做其他事情,但我认为这是公平的游戏)
Named tuples: 154.0662612915039 seconds.
Autodict: 0.1483476161956787 seconds.
您可以使用 frozenset()
制作一个最小的 class 来存储数据,然后添加自定义 __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
我想要一个 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})
我在评论中提到过,我不确定为什么需要这样做。
但是可以简单地覆盖 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
循环有关,并且可能可以通过某种方式轻松补救。但是对于基本的理解,这是一个很大的不同。该示例显然不会测试更大的一次性创建或访问时间。只是,"what if you use these options to create data-sets over a period of time, how much time would you loose" :)
奖金:如果你有一个很大的基础词典,并且想冻结它怎么办?
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 做其他事情,但我认为这是公平的游戏)
Named tuples: 154.0662612915039 seconds.
Autodict: 0.1483476161956787 seconds.
您可以使用 frozenset()
制作一个最小的 class 来存储数据,然后添加自定义 __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