เปลี่ยนชื่อคีย์พจนานุกรม


400

มีวิธีการเปลี่ยนชื่อคีย์พจนานุกรมโดยไม่ต้องกำหนดค่าใหม่ให้เป็นชื่อใหม่และลบคีย์ชื่อเก่าออกหรือไม่ และไม่มีการวนซ้ำผ่านคีย์ / ค่า dict?

ในกรณีของ OrderedDict ให้ทำเช่นเดียวกันในขณะที่รักษาตำแหน่งของคีย์นั้น


7
คุณหมายความว่าอย่างไรโดย "โดยไม่ต้องกำหนดค่าให้เป็นชื่อใหม่และลบคีย์ชื่อเก่า" วิธีที่ฉันเห็นนั่นคือคำจำกัดความของการเปลี่ยนชื่อคีย์และคำตอบทั้งหมดด้านล่างกำหนดค่าใหม่และลบชื่อคีย์เก่า คุณยังต้องยอมรับคำตอบดังนั้นสิ่งเหล่านี้อาจไม่ประสบความสำเร็จในสิ่งที่คุณกำลังมองหา?
dbliss

5
คุณต้องระบุหมายเลขเวอร์ชันจริงๆ ในฐานะที่เป็นของงูใหญ่ 3.7 สเปคภาษาตอนนี้รับประกันว่า dicts ปฏิบัติตามคำสั่งแทรก สิ่งนี้ยังทำให้ OrderedDict ล้าสมัยเป็นส่วนใหญ่ (เว้นแต่ก) คุณต้องการโค้ดที่ backport เป็น 2.x หรือ 3.6- หรือ b) คุณสนใจเกี่ยวกับปัญหาที่ระบุในOrderedDict จะกลายเป็นซ้ำซ้อนใน Python 3.7 หรือไม่ ) และกลับมาที่ 3.6 ลำดับการแทรกพจนานุกรมรับประกันโดยการใช้งาน CPython (แต่ไม่ใช่สเป็คภาษา)
smci

1
@smci ใน Python 3.7 dicts ปฏิบัติตามคำสั่งแทรก OrderedDictแต่ยังคงมีความแตกต่างจาก ไม่สนใจคำสั่งเมื่อเทียบกับความเท่าเทียมกัน dicts ขณะที่OrderedDictคำสั่งเมื่อพิจารณาเปรียบเทียบ ฉันรู้ว่าคุณเชื่อมโยงกับบางสิ่งที่อธิบายได้ แต่ฉันคิดว่าความคิดเห็นของคุณอาจทำให้ผู้ที่อาจไม่ได้อ่านลิงก์นั้นเข้าใจผิด
Flimm

@Flimm: จริง แต่ฉันไม่เห็นว่ามันสำคัญคำถามนี้ถามคำถามสองคำถามในหนึ่งเดียวและความตั้งใจของส่วนที่สองถูกแทนที่ "ไม่สนใจลำดับเมื่อพวกเขาถูกเปรียบเทียบเพื่อความเท่าเทียม"ใช่เพราะพวกเขาไม่ควรเปรียบเทียบตามลำดับ "ในขณะที่OrderedDictคำนึงถึงการสั่งซื้อเมื่อเปรียบเทียบ"ตกลง แต่ไม่มีใครสนใจโพสต์ 3.7 ฉันอ้างว่าOrderedDictล้าสมัยเป็นส่วนใหญ่คุณสามารถบอกเหตุผลได้ไหมว่าทำไมมันถึงไม่ใช่? เช่นที่นี่จะไม่โน้มน้าวใจจนกว่าคุณจะต้องการreversed()
smci

@Flimm: ฉันไม่พบข้อโต้แย้งที่น่าเชื่อถือว่าทำไม OrderedDict ไม่ล้าสมัยในรหัสใน 3.7+ ยกเว้นว่าจะต้องเข้ากันได้กับ pre-3.7 หรือ 2.x เช่นนี้ไม่โน้มน้าวใจเลย โดยเฉพาะอย่างยิ่ง"การใช้ OrderedDict สื่อสารความตั้งใจของคุณ ... "เป็นอาร์กิวเมนต์ที่น่าตกใจสำหรับหนี้ทางเทคนิคที่จำเป็นอย่างสมบูรณ์และการทำให้งงงวย ผู้คนควรเปลี่ยนกลับไปเขียนตามคำบอกและลงมือทำ ง่าย ๆ
smci

คำตอบ:


712

สำหรับ dict ปกติคุณสามารถใช้:

mydict[new_key] = mydict.pop(old_key)

สำหรับ OrderedDict ฉันคิดว่าคุณต้องสร้างใหม่ทั้งหมดโดยใช้ความเข้าใจ

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

การแก้ไขคีย์เองตามที่คำถามนี้ดูเหมือนว่าจะถามนั้นเป็นไปไม่ได้เพราะคีย์ดิชมักจะเป็นวัตถุที่ไม่เปลี่ยนรูปเช่นตัวเลขสตริงหรือทูเปิล แทนที่จะพยายามปรับเปลี่ยนคีย์การกำหนดค่าให้กับคีย์ใหม่และการลบคีย์เก่าคือวิธีที่คุณจะได้รับ "เปลี่ยนชื่อ" ในไพ ธ อน


สำหรับ python 3.5 ฉันคิดว่าdict.popมันใช้การได้กับ OrderedDict ตามการทดสอบของฉัน
Daniel

5
ไม่OrderedDictจะรักษาลำดับการแทรกซึ่งไม่ใช่สิ่งที่คำถามถาม
Wim

64

วิธีที่ดีที่สุดใน 1 บรรทัด:

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

3
ถ้าคุณมีd = {('test', 'foo'):[0,1,2]}อะไร
Petr Fedosov

4
@PetrFedosov แล้วคุณจะทำอย่างไรd[('new', 'key')] = d.pop(('test', 'foo'))
Robert Siemer

17
คำตอบนี้มาสองปีหลังจากคำตอบของ wim ซึ่งแนะนำสิ่งเดียวกันโดยไม่มีข้อมูลเพิ่มเติม ฉันพลาดอะไรไป
Andras Deak

@AndrasDeak ความแตกต่างระหว่าง dict () และ OrderedDict ()
Tcll

4
คำตอบของ wim ในการแก้ไขครั้งแรกตั้งแต่ปี 2013มีการเพิ่มเติมเท่านั้นตั้งแต่ ความเป็นระเบียบมาจากเกณฑ์ของ OP เท่านั้น
Andras Deak

29

ใช้การตรวจสอบnewkey!=oldkeyด้วยวิธีนี้คุณสามารถทำได้:

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

4
ใช้งานได้อย่างสวยงาม แต่ไม่รักษาลำดับเดิมเพราะคีย์ใหม่จะถูกเพิ่มในตอนท้ายโดยค่าเริ่มต้น (ใน python3)
szeitlin

2
@szeitlin คุณไม่ควรพึ่งพาdictคำสั่งซื้อแม้ว่า python3.6 + จะเน้นย้ำในรูปแบบที่สั่งซื้อ แต่รุ่นก่อนหน้านี้ก็ไม่ได้และมันก็ไม่ใช่ฟีเจอร์ที่เป็นผลมาจากการเปลี่ยนแปลงของ py3.6 + dicts ใช้OrderedDictถ้าคุณสนใจสั่งซื้อ
Granitosaurus

2
ขอบคุณ @Granitosaurus ฉันไม่ต้องการให้คุณอธิบายให้ฉัน นั่นไม่ใช่ประเด็นของความคิดเห็นของฉัน
szeitlin

5
@szeitlin ความคิดเห็นของคุณบอกเป็นนัยว่าความจริงที่ว่ามันเปลี่ยนลำดับ dict มีความสำคัญไม่ว่าจะเป็นรูปร่างหรือรูปแบบในความเป็นจริงไม่มีใครควรพึ่งพาคำสั่งพจนานุกรมดังนั้นข้อเท็จจริงนี้จึงไม่เกี่ยวข้องอย่างสมบูรณ์
Granitosaurus

11

ในกรณีที่เปลี่ยนชื่อคีย์พจนานุกรมทั้งหมด:

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
มีการtarget_dict.keys()รับประกันว่าจะสั่งซื้อเช่นเดียวกับในความหมาย? ฉันคิดว่า
Pict

ฉันคิดว่าคุณควรจัดเรียงคีย์ของคุณในบางจุด ...
Espoir Murhabazi

10

คุณสามารถใช้สิ่งนี้OrderedDict recipeเขียนโดย Raymond Hettinger และปรับเปลี่ยนเพื่อเพิ่มrenameวิธีการ แต่สิ่งนี้จะเป็น 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

1
@Mose เลือกเพิ่มไฟล์ Python ที่ใดที่หนึ่งในพา ธ การค้นหาของโมดูลและนำเข้าOrderedDictจากนั้น แต่ฉันอยากจะแนะนำ: สร้างคลาสที่สืบทอดจากOrderedDictและเพิ่มrenameวิธีการ
Ashwini Chaudhary

ดูเหมือนว่า OrderedDict จะได้รับการเขียนใหม่เพื่อใช้รายการที่เชื่อมโยงเป็นทวีคูณดังนั้นจึงอาจมีวิธีการทำเช่นนี้ แต่ต้องใช้การแสดงกายกรรมมากขึ้น : - /
szeitlin

6

มีบางคนก่อนหน้านี้ที่ฉันพูดถึง.popเคล็ดลับในการลบและสร้างรหัสในหนึ่งซับ

ฉันพบว่าการใช้งานที่ชัดเจนยิ่งขึ้นสามารถอ่านได้มากขึ้น

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

รหัสข้างต้นกลับมา {'a': 1, 'c': 2}


1

คำตอบอื่น ๆ ค่อนข้างดี แต่ใน python3.6 dict ปกติก็มีคำสั่งด้วยเช่นกัน ดังนั้นจึงเป็นการยากที่จะรักษาตำแหน่งของคีย์ในกรณีปกติ

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

ใน 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}

อาจเป็นเรื่องน่าสังเกตว่าหากไม่มีprintคำสั่งแล้ว ipython console / jupyter notebook จะแสดงพจนานุกรมตามลำดับการเลือก ...


0

ฉันมากับฟังก์ชั่นนี้ซึ่งไม่ได้กลายพันธุ์พจนานุกรมดั้งเดิม ฟังก์ชั่นนี้ยังรองรับรายการพจนานุกรมด้วย

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

ฉันใช้คำตอบของ @wim ข้างต้นกับ dict.pop () เมื่อเปลี่ยนชื่อคีย์ แต่ฉันพบ gotcha หมุนเวียนไปตามคำสั่งเพื่อเปลี่ยนคีย์โดยไม่ต้องแยกรายการของคีย์เก่าทั้งหมดจากอินสแตนซ์ของ dict ส่งผลให้เกิดการวนซ้ำใหม่เปลี่ยนคีย์เข้าสู่วงและหายไปบางคีย์ที่มีอยู่

เพื่อเริ่มต้นด้วยฉันทำอย่างนี้:

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

ฉันพบว่าการขี่จักรยานผ่าน dict ด้วยวิธีนี้พจนานุกรมก็ยังคงหากุญแจอยู่แม้ว่ามันจะไม่ควรทำเช่นปุ่มใหม่ตัวที่ฉันเปลี่ยนไป! ฉันต้องการแยกอินสแตนซ์ทั้งหมดออกจากกันเป็น (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 () ทำให้ฉันผูกกับอินสแตนซ์ดั้งเดิมด้วยผลข้างเคียงที่แปลกประหลาด


-1

ในกรณีที่มีคนต้องการเปลี่ยนชื่อคีย์ทั้งหมดในครั้งเดียวให้รายชื่อใหม่:

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 ฉันชอบฟังก์ชั่นของคุณ นี่คือการดัดแปลงเพื่อเปลี่ยนชื่อหลายคีย์ในการโทรครั้งเดียว:

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

สมมติว่าคุณต้องการเปลี่ยนชื่อคีย์ k3 เป็น k4:

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

1
ไม่ทำงานคุณหมายถึง ... temp_dict ['k4'] = temp_dict.pop ('k3') หรือไม่
DougR

สิ่งนี้จะส่งคืนTypeError: 'dict' object is not callableถ้าหากtemp_dict('k3')คุณพยายามอ้างอิงค่าของk3คีย์จากนั้นคุณควรใช้วงเล็บเหลี่ยมไม่ใช่วงเล็บ อย่างไรก็ตามจะเป็นการเพิ่มคีย์ใหม่ให้กับพจนานุกรมและจะไม่เปลี่ยนชื่อคีย์ที่มีอยู่ตามที่ร้องขอ
จัสมิน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.