Đổi tên khóa từ điển


401

Có cách nào để đổi tên một khóa từ điển, mà không cần gán lại giá trị của nó thành một tên mới và loại bỏ khóa tên cũ; và không lặp lại thông qua khóa / giá trị dict?

Trong trường hợp OrderedDict, hãy làm tương tự, trong khi vẫn giữ vị trí của khóa đó.


7
chính xác ý bạn là gì khi "không gán lại giá trị của nó cho một tên mới và xóa khóa tên cũ"? theo cách tôi thấy, đó là định nghĩa đổi tên khóa và tất cả các câu trả lời dưới đây sẽ gán lại giá trị và xóa tên khóa cũ. bạn vẫn chưa chấp nhận câu trả lời, vì vậy có lẽ những điều này chưa hoàn thành những gì bạn đang tìm kiếm?
dbliss

5
Bạn thực sự cần chỉ định số phiên bản. Kể từ Python 3.7, thông số ngôn ngữ hiện đảm bảo rằng các ký tự tuân theo thứ tự chèn . Điều đó cũng làm cho OrderedDict hầu hết bị lỗi thời (trừ khi a) bạn muốn mã cũng sao lưu thành 2.x hoặc 3.6- hoặc b) mà bạn quan tâm về các vấn đề được liệt kê trong Will OrderedDict trở nên dư thừa trong Python 3.7? ). Và trở lại năm 3.6, thứ tự chèn từ điển được đảm bảo bằng cách triển khai CPython (nhưng không phải là thông số ngôn ngữ).
smci

1
@smci Trong Python 3.7 dicts tuân theo thứ tự chèn, tuy nhiên vẫn có khác OrderedDict. dicts bỏ qua trật tự khi chúng được so sánh cho sự bình đẳng, trong khi OrderedDicttính đến trật tự khi được so sánh. Tôi biết bạn đã liên kết với một cái gì đó giải thích nó, nhưng tôi nghĩ nhận xét của bạn có thể đã đánh lừa những người không đọc được liên kết đó.
Flimm

@Flimm: điều đó đúng nhưng tôi không thể thấy nó có vấn đề, câu hỏi này đặt ra hai câu hỏi trong một và ý định của phần thứ hai đã được thay thế. "Dicts bỏ qua trật tự khi chúng được so sánh cho sự bình đẳng" Có bởi vì chúng không được so sánh theo thứ tự. "Trong khi OrderedDicttính đến thứ tự khi được so sánh" Ok nhưng không ai quan tâm đến hậu 3.7. Tôi khẳng định OrderedDictphần lớn đã lỗi thời, bạn có thể nói rõ lý do tại sao không? ví dụ ở đây không thuyết phục trừ khi bạn cầnreversed()
smci

@Flimm: Tôi không thể tìm thấy bất kỳ đối số đáng tin cậy nào tại sao OrderedDict không bị lỗi thời đối với mã trong 3.7+ trừ khi nó phải tương thích với tiền 3.7 hoặc 2.x. Vì vậy, ví dụ như điều này không thuyết phục. Cụ thể, "sử dụng OrderedDict truyền đạt ý định của bạn ..." là một lập luận kinh khủng cho nợ kỹ thuật và che giấu hoàn toàn cần thiết. Mọi người chỉ cần chuyển trở lại dict và tiếp tục với nó. Thật đơn giản.
smci

Câu trả lời:


712

Đối với một lệnh chính quy, bạn có thể sử dụng:

mydict[new_key] = mydict.pop(old_key)

Đối với một OrderedDict, tôi nghĩ bạn phải xây dựng một cái hoàn toàn mới bằng cách sử dụng một sự hiểu biết.

>>> OrderedDict(zip('123', 'abc'))
OrderedDict([('1', 'a'), ('2', 'b'), ('3', 'c')])
>>> oldkey, newkey = '2', 'potato'
>>> OrderedDict((newkey if k == oldkey else k, v) for k, v in _.viewitems())
OrderedDict([('1', 'a'), ('potato', 'b'), ('3', 'c')])

Tự sửa đổi khóa, vì câu hỏi này dường như đang được hỏi, là không thực tế vì các khóa chính thường là các đối tượng bất biến như số, chuỗi hoặc bộ dữ liệu. Thay vì cố gắng sửa đổi khóa, việc gán lại giá trị cho khóa mới và xóa khóa cũ là cách bạn có thể đạt được "đổi tên" trong python.


Đối với python 3.5, tôi nghĩ dict.popcó thể thực hiện được đối với OrderedDict dựa trên thử nghiệm của tôi.
Daniel

5
Không, OrderedDictsẽ duy trì thứ tự chèn, đó không phải là những gì câu hỏi được hỏi về.
wim

64

phương pháp tốt nhất trong 1 dòng:

>>> d = {'test':[0,1,2]}
>>> d['test2'] = d.pop('test')
>>> d
{'test2': [0, 1, 2]}

3
nếu bạn có d = {('test', 'foo'):[0,1,2]}thì sao
Petr Fedosov

4
@PetrFedoov, sau đó bạn làmd[('new', 'key')] = d.pop(('test', 'foo'))
Robert Siemer

17
Câu trả lời này được đưa ra hai năm sau câu trả lời của wim cho thấy điều tương tự chính xác, không có thêm thông tin. Tôi đang thiếu gì?
Andras Deak

@AndrasDeak sự khác biệt giữa một dict () và OrderedDict ()
Tcll

4
Câu trả lời của wim trong lần sửa đổi đầu tiên từ năm 2013 , chỉ có những bổ sung kể từ đó. Sự ngăn nắp chỉ xuất phát từ tiêu chí của OP.
Andras Deak

29

Sử dụng kiểm tra newkey!=oldkey, theo cách này bạn có thể làm:

if newkey!=oldkey:  
    dictionary[newkey] = dictionary[oldkey]
    del dictionary[oldkey]

4
cái này hoạt động rất đẹp, nhưng không duy trì thứ tự ban đầu vì khóa mới được thêm vào cuối theo mặc định (trong python3).
szeitlin

2
@szeitlin bạn không nên dựa vào dictthứ tự, ngay cả khi python3.6 + xác định nó ở dạng đã đặt hàng, các phiên bản trước đó không thực sự là một tính năng chỉ là hậu quả của việc thay đổi py3.6 +. Sử dụng OrderedDictnếu bạn quan tâm đến trật tự.
Granitosaurus

2
Cảm ơn @Granitosaurus, tôi không cần bạn giải thích điều đó với tôi. Đó không phải là điểm nhận xét của tôi.
szeitlin

5
@szeitlin bình luận của bạn ngụ ý rằng thực tế là nó thay đổi thứ tự chính tả theo một cách nào đó, hình dạng hoặc hình thức khi trong thực tế không ai nên dựa vào thứ tự từ điển nên thực tế này hoàn toàn không liên quan
Granitosaurus 16/11/18

11

Trong trường hợp đổi tên tất cả các khóa từ điển:

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)

1
Được target_dict.keys()đảm bảo là cùng thứ tự như trong định nghĩa? Tôi nghĩ rằng một lệnh của Python là không có thứ tự và thứ tự từ chế độ xem khóa của chính tả là không thể đoán trước
ttimasdf

Tôi nghĩ bạn nên sắp xếp chìa khóa của mình tại một số điểm ...
Espoir Murhabazi

10

Bạn có thể sử dụng điều này OrderedDict recipeđược viết bởi Raymond Hettinger và sửa đổi nó để thêm một renamephương thức, nhưng điều này sẽ là một O (N) phức tạp:

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

Thí dụ:

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

đầu ra:

OrderedDict({'a': 1, 'b': 2, 'c': 3})
foo 1
bar 2
c 3
spam 5

1
@Merose Thêm tệp Python ở đâu đó trong đường dẫn tìm kiếm mô-đun của bạn và nhập OrderedDicttừ nó. Nhưng tôi muốn giới thiệu: Tạo một lớp kế thừa từ OrderedDictvà thêm một renamephương thức vào nó.
Ashwini Chaudhary

Có vẻ như OrderedDict đã được viết lại để sử dụng danh sách liên kết đôi, vì vậy có lẽ vẫn còn một cách để làm điều này, nhưng nó đòi hỏi nhiều màn nhào lộn hơn. : - /
szeitlin

6

Một vài người trước tôi đã đề cập đến .popthủ thuật xóa và tạo khóa trong một lớp lót.

Cá nhân tôi thấy việc thực hiện rõ ràng hơn dễ đọc hơn:

d = {'a': 1, 'b': 2}
v = d['b']
del d['b']
d['c'] = v

Mã trên trả về {'a': 1, 'c': 2}


1

Các câu trả lời khác là khá tốt. Nhưng trong python3.6, dict thường cũng có thứ tự. Vì vậy, thật khó để giữ vị trí quan trọng trong trường hợp bình thường.

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

1

Trong Python 3.6 (trở đi?) Tôi sẽ sử dụng một lớp lót sau

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()))

sản xuất

{'a': 1, 'new': 4, 'c': 3}

Có thể đáng chú ý rằng không có printtuyên bố, máy tính xách tay ipython console / jupyter trình bày từ điển theo thứ tự lựa chọn của họ ...


0

Tôi đã đưa ra chức năng này mà không làm thay đổi từ điển gốc. Chức năng này cũng hỗ trợ danh sách từ điển.

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))

-1

Tôi đang sử dụng câu trả lời của @wim ở trên, với dict.pop () khi đổi tên các khóa, nhưng tôi đã tìm thấy một gotcha. Đạp xe qua dict để thay đổi các phím, mà không tách hoàn toàn danh sách các khóa cũ khỏi trường hợp dict, dẫn đến việc đạp xe mới, thay đổi các phím vào vòng lặp và thiếu một số khóa hiện có.

Để bắt đầu, tôi đã làm theo cách này:

for current_key in my_dict:
    new_key = current_key.replace(':','_')
    fixed_metadata[new_key] = fixed_metadata.pop(current_key)

Tôi thấy rằng đạp xe qua dict theo cách này, từ điển tiếp tục tìm khóa ngay cả khi không nên, tức là các khóa mới, những khóa tôi đã thay đổi! Tôi cần tách hoàn toàn các thể hiện với nhau để (a) tránh tìm khóa thay đổi của riêng tôi trong vòng lặp for và (b) tìm một số khóa không được tìm thấy trong vòng lặp vì một số lý do.

Tôi đang làm điều này bây giờ:

current_keys = list(my_dict.keys())
for current_key in current_keys:
    and so on...

Việc chuyển đổi my_dict.keys () thành một danh sách là cần thiết để không bị tham chiếu đến lệnh thay đổi. Chỉ cần sử dụng my_dict.keys () đã giữ cho tôi gắn liền với phiên bản gốc, với các hiệu ứng phụ lạ.


-1

Trong trường hợp ai đó muốn đổi tên tất cả các khóa cùng một lúc, cung cấp một danh sách với các tên mới:

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()}

-1

@ helloswift123 Tôi thích chức năng của bạn. Đây là một sửa đổi để đổi tên nhiều khóa trong một cuộc gọi:

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

-2

Giả sử bạn muốn đổi tên khóa k3 thành k4:

temp_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
temp_dict['k4']= temp_dict.pop('k3')

1
Không hoạt động, ý bạn là ... temp_dict ['k4'] = temp_dict.pop ('k3')?
DougR

Điều này sẽ trả về một TypeError: 'dict' object is not callableNếu temp_dict('k3')bạn đang cố gắng tham chiếu giá trị của k3khóa thì bạn nên sử dụng dấu ngoặc vuông và không phải dấu ngoặc đơn. Tuy nhiên, điều này sẽ chỉ thêm một khóa mới vào từ điển và sẽ không đổi tên một khóa hiện có, theo yêu cầu.
Jasmine
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.