是否有一种方法重命名字典键,而不重新分配其值为一个新名称和删除旧的名称键;并且没有通过dict键/值迭代?
在OrderedDict的情况下,做同样的事情,同时保持该键的位置。
是否有一种方法重命名字典键,而不重新分配其值为一个新名称和删除旧的名称键;并且没有通过dict键/值迭代?
在OrderedDict的情况下,做同样的事情,同时保持该键的位置。
使用newkey检查!=oldkey,这样你可以做:
if newkey!=oldkey:
dictionary[newkey] = dictionary[oldkey]
del dictionary[oldkey]
对于一个普通的字典,你可以使用:
mydict[k_new] = mydict.pop(k_old)
这将把项移动到dict的末尾,除非k_new已经存在,在这种情况下,它将覆盖原地的值。
对于Python 3.7+字典,您还想保留顺序,最简单的方法是重新构建一个全新的实例。例如,将键2重命名为' 2 ':
>>> d = {0:0, 1:1, 2:2, 3:3}
>>> {"two" if k == 2 else k:v for k,v in d.items()}
{0: 0, 1: 1, 'two': 2, 3: 3}
OrderedDict也是如此,不能使用dict理解语法,但可以使用生成器表达式:
OrderedDict((k_new if k == k_old else k, v) for k, v in od.items())
像问题所要求的那样修改键本身是不切实际的,因为键是可哈希的,这通常意味着它们是不可变的,不能被修改。
您可以使用Raymond Hettinger编写的OrderedDict食谱,并修改它以添加重命名方法,但这将是一个O(N)的复杂性:
def rename(self,key,new_key):
ind = self._keys.index(key) #get the index of old key, O(N) operation
self._keys[ind] = new_key #replace old key with new key in self._keys
self[new_key] = self[key] #add the new key, this is added at the end of self._keys
self._keys.pop(-1) #pop the last item in self._keys
例子:
dic = OrderedDict((("a",1),("b",2),("c",3)))
print dic
dic.rename("a","foo")
dic.rename("b","bar")
dic["d"] = 5
dic.rename("d","spam")
for k,v in dic.items():
print k,v
输出:
OrderedDict({'a': 1, 'b': 2, 'c': 3})
foo 1
bar 2
c 3
spam 5
在我之前的一些人提到了.pop技巧,可以在一行程序中删除和创建一个键。
我个人认为更显式的实现更具可读性:
d = {'a': 1, 'b': 2}
v = d['b']
del d['b']
d['c'] = v
上面的代码返回{'a': 1, 'c': 2}
其他答案也很不错。但在python3.6中,常规字典也有顺序。所以在正常情况下很难保持键的位置。
def rename(old_dict,old_name,new_name):
new_dict = {}
for key,value in zip(old_dict.keys(),old_dict.values()):
new_key = key if key != old_name else new_name
new_dict[new_key] = old_dict[key]
return new_dict
我使用@wim的答案上面,dict.pop()重命名键时,但我发现了一个问题。循环遍历dict以更改键,而没有将旧键列表与dict实例完全分离,结果将新的、已更改的键循环到循环中,并丢失一些现有的键。
首先,我是这样做的:
for current_key in my_dict:
new_key = current_key.replace(':','_')
fixed_metadata[new_key] = fixed_metadata.pop(current_key)
我发现,以这种方式循环字典,字典一直在找到键,即使它不应该,也就是说,新的键,我已经改变的那些!我需要将实例彼此完全分开,以(a)避免在for循环中找到我自己更改的键,以及(b)找到由于某种原因在循环中没有找到的一些键。
我现在正在做这件事:
current_keys = list(my_dict.keys())
for current_key in current_keys:
and so on...
必须将my_dict.keys()转换为列表,以摆脱对不断变化的dict的引用。仅使用my_dict.keys()将我与原始实例绑定在一起,并产生奇怪的副作用。
如果有人想要一次重命名所有的键,提供一个包含新名称的列表:
def rename_keys(dict_, new_keys):
"""
new_keys: type List(), must match length of dict_
"""
# dict_ = {oldK: value}
# d1={oldK:newK,} maps old keys to the new ones:
d1 = dict( zip( list(dict_.keys()), new_keys) )
# d1{oldK} == new_key
return {d1[oldK]: value for oldK, value in dict_.items()}
Python 3.6(以后?)我会用下面的一句话
test = {'a': 1, 'old': 2, 'c': 3}
old_k = 'old'
new_k = 'new'
new_v = 4 # optional
print(dict((new_k, new_v) if k == old_k else (k, v) for k, v in test.items()))
生产
{'a': 1, 'new': 4, 'c': 3}
可能值得注意的是,如果没有打印语句,ipython控制台/jupyter笔记本将按照它们选择的顺序显示字典…
假设你想把键k3重命名为k4:
temp_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
temp_dict['k4']= temp_dict.pop('k3')
重命名所有字典键的情况:
target_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
new_keys = ['k4','k5','k6']
for key,n_key in zip(target_dict.keys(), new_keys):
target_dict[n_key] = target_dict.pop(key)
@helloswift123我喜欢你的功能。下面是在一次调用中重命名多个键的修改:
def rename(d, keymap):
"""
:param d: old dict
:type d: dict
:param keymap: [{:keys from-keys :values to-keys} keymap]
:returns: new dict
:rtype: dict
"""
new_dict = {}
for key, value in zip(d.keys(), d.values()):
new_key = keymap.get(key, key)
new_dict[new_key] = d[key]
return new_dict
我想出了这个函数,它不会改变原来的字典。这个函数也支持字典列表。
import functools
from typing import Union, Dict, List
def rename_dict_keys(
data: Union[Dict, List[Dict]], old_key: str, new_key: str
):
"""
This function renames dictionary keys
:param data:
:param old_key:
:param new_key:
:return: Union[Dict, List[Dict]]
"""
if isinstance(data, dict):
res = {k: v for k, v in data.items() if k != old_key}
try:
res[new_key] = data[old_key]
except KeyError:
raise KeyError(
"cannot rename key as old key '%s' is not present in data"
% old_key
)
return res
elif isinstance(data, list):
return list(
map(
functools.partial(
rename_dict_keys, old_key=old_key, new_key=new_key
),
data,
)
)
raise ValueError("expected type List[Dict] or Dict got '%s' for data" % type(data))
我结合了上面帖子中的一些答案,并提出了下面的解决方案。虽然它很简单,但它可以用作从字典中进行更复杂的键更新的构建块。
test_dict = {'a': 1, 'b': 2, 'c': 3}
print(test_dict)
# {'a': 1, 'b': 2, 'c': 3}
prefix = 'up'
def dict_key_update(json_file):
new_keys = []
old_keys = []
for i,(key,value) in enumerate(json_file.items()):
old_keys.append(key)
new_keys.append(str(prefix) + key) # i have updated by adding a prefix to the
# key
for old_key, new_key in zip(old_keys,new_keys):
print('old {}, new {}'.format(old_key, new_key))
if new_key!=old_key:
json_file[new_key] = json_file.pop(old_key)
return json_file
test_dict = dict_key_update(test_dict)
print(test_dict)
# {'upa': 1, 'upb': 2, 'upc': 3}
你可以使用下面的代码:
OldDict={'a':'v1', 'b':'v2', 'c':'v3'}
OldKey=['a','b','c']
NewKey=['A','B','C']
def DictKeyChanger(dict,OldKey,NewKey):
ListAllKey=list(dict.keys())
for x in range(0,len(NewKey)):
dict[NewKey[x]]=dict[OldKey[x]] if OldKey[x] in ListAllKey else None
for x in ListAllKey:
dict.pop(x)
return dict
NewDict=DictKeyChanger(OldDict,OldKey,NewKey)
print(NewDict)#===>>{'A': 'v1', 'B': 'v2', 'C': 'v3'}
注:
列表OldKey和列表NewKey的长度必须相等。 列表OldKey的长度必须等于listNewKey,如果该键在OldKey中不存在,则将'noexis'改为如下所示。
例子:
OldDict={'a':'v1', 'b':'v2', 'c':'v3'}
OldKey=['a','b','c','noexis','noexis']
NewKey=['A','B','C','D','E']
NewDict=DictKeyChanger(OldDict,OldKey,NewKey)
print(NewDict)#===>>{'A': 'v1', 'B': 'v2', 'C': 'v3', 'D': None, 'E': None}
对于保持顺序的情况(另一个是平凡的,删除旧的并添加新的):我对需要重构的有序字典不满意(至少部分),显然是出于效率的原因,所以我组合了一个类(OrderedDictX),它扩展了OrderedDict,并允许您高效地进行键更改,即在O(1)复杂度中。该实现还可以针对现在已排序的内置dict类进行调整。
它使用2个额外的字典来重新映射已更改的键(“外部”-例如,当它们出现在用户外部时)到底层OrderedDict中的键(“内部”)-字典将只保存已更改的键,因此只要没有更改键,它们将为空。
性能测量:
import timeit
import random
# Efficiency tests
from collections import MutableMapping
class OrderedDictRaymond(dict, MutableMapping):
def __init__(self, *args, **kwds):
if len(args) > 1:
raise TypeError('expected at 1 argument, got %d', len(args))
if not hasattr(self, '_keys'):
self._keys = []
self.update(*args, **kwds)
def rename(self,key,new_key):
ind = self._keys.index(key) #get the index of old key, O(N) operation
self._keys[ind] = new_key #replace old key with new key in self._keys
self[new_key] = self[key] #add the new key, this is added at the end of self._keys
self._keys.pop(-1) #pop the last item in self._keys
dict.__delitem__(self, key)
def clear(self):
del self._keys[:]
dict.clear(self)
def __setitem__(self, key, value):
if key not in self:
self._keys.append(key)
dict.__setitem__(self, key, value)
def __delitem__(self, key):
dict.__delitem__(self, key)
self._keys.remove(key)
def __iter__(self):
return iter(self._keys)
def __reversed__(self):
return reversed(self._keys)
def popitem(self):
if not self:
raise KeyError
key = self._keys.pop()
value = dict.pop(self, key)
return key, value
def __reduce__(self):
items = [[k, self[k]] for k in self]
inst_dict = vars(self).copy()
inst_dict.pop('_keys', None)
return (self.__class__, (items,), inst_dict)
setdefault = MutableMapping.setdefault
update = MutableMapping.update
pop = MutableMapping.pop
keys = MutableMapping.keys
values = MutableMapping.values
items = MutableMapping.items
def __repr__(self):
pairs = ', '.join(map('%r: %r'.__mod__, self.items()))
return '%s({%s})' % (self.__class__.__name__, pairs)
def copy(self):
return self.__class__(self)
@classmethod
def fromkeys(cls, iterable, value=None):
d = cls()
for key in iterable:
d[key] = value
return d
class obj_container:
def __init__(self, obj) -> None:
self.obj = obj
def change_key_splice(container, k_old, k_new):
od = container.obj
container.obj = OrderedDict((k_new if k == k_old else k, v) for k, v in od.items())
def change_key_raymond(container, k_old, k_new):
od = container.obj
od.rename(k_old, k_new)
def change_key_odx(container, k_old, k_new):
odx = container.obj
odx.change_key(k_old, k_new)
NUM_ITEMS = 20000
od_splice = OrderedDict([(x, x) for x in range(NUM_ITEMS)])
od_raymond = OrderedDictRaymond(od_splice.items())
odx = OrderedDictX(od_splice.items())
od_splice, od_raymond, odx = [obj_container(d) for d in [od_splice, od_raymond, odx]]
assert odx.obj == od_splice.obj
assert odx.obj == od_raymond.obj
# Pick randomly half of the keys to change
keys_to_change = random.sample(range(NUM_ITEMS), NUM_ITEMS//2)
print(f'OrderedDictX: {timeit.timeit(lambda: [change_key_odx(odx, k, k+NUM_ITEMS) for k in keys_to_change], number=1)}')
print(f'OrderedDictRaymond: {timeit.timeit(lambda: [change_key_raymond(od_raymond, k, k+NUM_ITEMS) for k in keys_to_change], number=1)}')
print(f'Splice: {timeit.timeit(lambda: [change_key_splice(od_splice, k, k+NUM_ITEMS) for k in keys_to_change], number=1)}')
assert odx.obj == od_splice.obj
assert odx.obj == od_raymond.obj
和结果:
OrderedDictX: 0.06587849999999995
OrderedDictRaymond: 1.1131364
Splice: 1165.2614647
正如预期的那样,拼接方法非常慢(但也没想到它会慢那么多),并且使用了大量内存,@Ashwini Chaudhary的O(N)解决方案(虽然已经修复了错误,但也需要del)也较慢,在本例中为17倍。
当然,这个解决方案是O(1),与O(N) OrderedDictRaymond相比,随着字典大小的增加,时间差变得更加明显,例如,对于5倍多的元素(100000),O(N)现在慢了100倍:
NUM_ITEMS = 100000
OrderedDictX: 0.3636919999999999
OrderedDictRaymond: 36.3963971
以下是代码,如果您看到问题或有改进建议,请评论,因为这可能仍然容易出错。
from collections import OrderedDict
class OrderedDictX(OrderedDict):
def __init__(self, *args, **kwargs):
# Mappings from new->old (ext2int), old->new (int2ext).
# Only the keys that are changed (internal key doesn't match what the user sees) are contained.
self._keys_ext2int = OrderedDict()
self._keys_int2ext = OrderedDict()
self.update(*args, **kwargs)
def change_key(self, k_old, k_new):
# Validate that the old key is part of the dict
if not self.__contains__(k_old):
raise Exception(f'Cannot rename key {k_old} to {k_new}: {k_old} not existing in dict')
# Return if no changing is actually to be done
if len(OrderedDict.fromkeys([k_old, k_new])) == 1:
return
# Validate that the new key would not conflict with another one
if self.__contains__(k_new):
raise Exception(f'Cannot rename key {k_old} to {k_new}: {k_new} already in dict')
# Change the key using internal dicts mechanism
if k_old in self._keys_ext2int:
# Revert change temporarily
k_old_int = self._keys_ext2int[k_old]
del self._keys_ext2int[k_old]
k_old = k_old_int
# Check if new key matches the internal key
if len(OrderedDict.fromkeys([k_old, k_new])) == 1:
del self._keys_int2ext[k_old]
return
# Finalize key change
self._keys_ext2int[k_new] = k_old
self._keys_int2ext[k_old] = k_new
def __contains__(self, k) -> bool:
if k in self._keys_ext2int:
return True
if not super().__contains__(k):
return False
return k not in self._keys_int2ext
def __getitem__(self, k):
if not self.__contains__(k):
# Intentionally raise KeyError in ext2int
return self._keys_ext2int[k]
return super().__getitem__(self._keys_ext2int.get(k, k))
def __setitem__(self, k, v):
if k in self._keys_ext2int:
return super().__setitem__(self._keys_ext2int[k], v)
# If the key exists in the internal state but was renamed to a k_ext,
# employ this trick: make it such that it appears as if k_ext has also been renamed to k
if k in self._keys_int2ext:
k_ext = self._keys_int2ext[k]
self._keys_ext2int[k] = k_ext
k = k_ext
return super().__setitem__(k, v)
def __delitem__(self, k):
if not self.__contains__(k):
# Intentionally raise KeyError in ext2int
del self._keys_ext2int[k]
if k in self._keys_ext2int:
k_int = self._keys_ext2int[k]
del self._keys_ext2int[k]
del self._keys_int2ext[k_int]
k = k_int
return super().__delitem__(k)
def __iter__(self):
yield from self.keys()
def __reversed__(self):
for k in reversed(super().keys()):
yield self._keys_int2ext.get(k, k)
def __eq__(self, other: object) -> bool:
if not isinstance(other, dict):
return False
if len(self) != len(other):
return False
for (k, v), (k_other, v_other) in zip(self.items(), other.items()):
if k != k_other or v != v_other:
return False
return True
def update(self, *args, **kwargs):
for k, v in OrderedDict(*args, **kwargs).items():
self.__setitem__(k, v)
def popitem(self, last=True) -> tuple:
if not last:
k = next(iter(self.keys()))
else:
k = next(iter(reversed(self.keys())))
v = self.__getitem__(k)
self.__delitem__(k)
return k, v
class OrderedDictXKeysView:
def __init__(self, odx: 'OrderedDictX', orig_keys):
self._odx = odx
self._orig_keys = orig_keys
def __iter__(self):
for k in self._orig_keys:
yield self._odx._keys_int2ext.get(k, k)
def __reversed__(self):
for k in reversed(self._orig_keys):
yield self._odx._keys_int2ext.get(k, k)
class OrderedDictXItemsView:
def __init__(self, odx: 'OrderedDictX', orig_items):
self._odx = odx
self._orig_items = orig_items
def __iter__(self):
for k, v in self._orig_items:
yield self._odx._keys_int2ext.get(k, k), v
def __reversed__(self):
for k, v in reversed(self._orig_items):
yield self._odx._keys_int2ext.get(k, k), v
def keys(self):
return self.OrderedDictXKeysView(self, super().keys())
def items(self):
return self.OrderedDictXItemsView(self, super().items())
def copy(self):
return OrderedDictX(self.items())
# FIXME: move this to pytest
if __name__ == '__main__':
MAX = 25
items = [(i+1, i+1) for i in range(MAX)]
keys = [i[0] for i in items]
d = OrderedDictX(items)
# keys() before change
print(list(d.items()))
assert list(d.keys()) == keys
# __contains__ before change
assert 1 in d
# __getitem__ before change
assert d[1] == 1
# __setitem__ before change
d[1] = 100
assert d[1] == 100
d[1] = 1
assert d[1] == 1
# __delitem__ before change
assert MAX in d
del d[MAX]
assert MAX not in d
d[MAX] = MAX
assert MAX in d
print('== Tests before key change finished ==')
# change_key and __contains__
assert MAX-1 in d
assert MAX*2 not in d
d.change_key(MAX-1, MAX*2)
assert MAX-1 not in d
assert MAX*2 in d
# items() and keys()
items[MAX-2] = (MAX*2, MAX-1)
keys[MAX-2] = MAX*2
assert list(d.items()) == items
assert list(d.keys()) == keys
print(list(d.items()))
# __getitem__
assert d[MAX*2] == MAX-1
# __setitem__
d[MAX*2] = MAX*3
items[MAX-2] = (MAX*2, MAX*3)
keys[MAX-2] = MAX*2
assert list(d.items()) == items
assert list(d.keys()) == keys
# __delitem__
del d[MAX]
items = items[:-1]
keys = keys[:-1]
assert list(d.items()) == items
assert list(d.keys()) == keys
d[MAX] = MAX
items.append((MAX, MAX))
keys.append(MAX)
# __iter__
assert list(d) == keys
# __reversed__
print(list(reversed(d.items())))
assert list(reversed(d)) == list(reversed(keys))
assert list(reversed(d.keys())) == list(reversed(keys))
assert list(reversed(d.items())) == list(reversed(items))
# pop_item()
assert d.popitem() == (MAX, MAX)
assert d.popitem() == (MAX*2, MAX*3)
items = items[:-2]
keys = keys[:-2]
assert list(d.items()) == items
assert list(d.keys()) == keys
# update()
d.update({1: 1000, MAX-2: MAX*4})
items[0] = (1, 1000)
items[MAX-3] = (MAX-2, MAX*4)
assert list(d.items()) == items
assert list(d.keys()) == keys
# move_to_end()
d.move_to_end(1)
items = items[1:] + [items[0]]
keys = keys[1:] + [keys[0]]
assert list(d.items()) == items
assert list(d.keys()) == keys
# __eq__
d.change_key(1, 2000)
other_d = OrderedDictX(d.items())
assert d == other_d
assert other_d == d