Memetakan nilai-nilai dalam kamus python

243

Diberikan kamus { k1: v1, k2: v2 ... }saya ingin mendapatkan { k1: f(v1), k2: f(v2) ... }asalkan saya melewati suatu fungsi f.

Apakah ada fungsi bawaan seperti itu? Atau harus saya lakukan

dict([(k, f(v)) for (k, v) in my_dictionary.iteritems()])

Idealnya saya hanya menulis

my_dictionary.map_values(f)

atau

my_dictionary.mutate_values_with(f)

Artinya, tidak masalah bagi saya jika kamus asli dimutasi atau salinan dibuat.

Tarrasch
sumber
2
Cara yang lebih baik untuk menulis contoh Anda adalah dict((k, f(v)) for k, v in mydict.iteritems()), yaitu tanpa tanda kurung, yang akan mencegah pembuatan daftar perantara melalui generator.
bereal

Jawaban:

355

Tidak ada fungsi seperti itu; cara termudah untuk melakukan ini adalah dengan menggunakan pemahaman dict:

my_dictionary = {k: f(v) for k, v in my_dictionary.items()}

Dalam python 2.7, gunakan .iteritems()metode alih-alih .items()untuk menghemat memori. Sintaks pemahaman dikt tidak diperkenalkan sampai python 2.7.

Perhatikan bahwa tidak ada metode seperti itu pada daftar juga; Anda harus menggunakan pemahaman daftar atau map()fungsinya.

Dengan demikian, Anda juga dapat menggunakan map()fungsi untuk memproses dict Anda:

my_dictionary = dict(map(lambda kv: (kv[0], f(kv[1])), my_dictionary.iteritems()))

tapi itu tidak bisa dibaca, sungguh.

Martijn Pieters
sumber
5
+1: inilah yang akan saya lakukan juga. dict(zip(a, map(f, a.values())))sedikit lebih pendek, tetapi saya harus berpikir tentang apa yang dilakukannya, dan mengingatkan diri sendiri bahwa ya, kunci dan nilai diulang dalam urutan yang sama jika dikt tidak berubah. Saya tidak perlu berpikir sama sekali tentang apa yang diktekan diktcomp, dan jadi itu jawaban yang tepat.
DSM
2
@ chiborg: itu karena alih-alih mencari semua pasangan nilai kunci dalam sekali jalan, Anda sekarang menggunakan jumlah my_dictionary.__getitem__panggilan kali .
Martijn Pieters
1
Perhatikan bahwa sejak PEP3113 (diimplementasikan dalam python 3.x) parameter tuple tidak didukung lagi: lambda (k,v): (k, f(v))harus ditulis ulang menjadi sesuatu sepertilambda k_v: (k_v[0], f(k_v[1]))
normanius
1
Mengapa parameter unpacking di-nixed? Bagaimana itu peningkatan ?
javadba
3
berasal dari bahasa FP, Python akan tampak sangat aneh.
juanchito
22

Anda dapat melakukan ini di tempat, daripada membuat dict baru, yang mungkin lebih disukai untuk kamus besar (jika Anda tidak memerlukan salinan).

def mutate_dict(f,d):
    for k, v in d.iteritems():
        d[k] = f(v)

my_dictionary = {'a':1, 'b':2}
mutate_dict(lambda x: x+1, my_dictionary)

hasil my_dictionarymengandung:

{'a': 2, 'b': 3}
gens
sumber
1
Keren, Anda mungkin harus mengganti nama mapdictmenjadi mutate_values_withatau sesuatu agar jelas bahwa Anda menulis ulang dikt! :)
Tarrasch
2
zip(d.keys(), d.values())berfungsi untuk lebih banyak versi daripadaiteritems()
ytpillai
1
@ytpillai 'zip' atau pemahaman membuat salinan, alih-alih mengubah nilai di tempat, yang merupakan tujuan dari jawaban saya. Jawaban yang diterima adalah jawaban terbaik ketika salinannya ok.
gens
1
Maaf, saya tidak menyadari Anda ingin menggunakan metode item. Namun, peningkatan lain untuk itu juga dimungkinkan (untuk pengguna non Python 2.7){k:f(v) for k,v in iter(d.items())}
ytpillai
1
Menghemat ruang dengan membuat iterator
ytpillai
13

Karena PEP-0469 yang mengubah nama iteritems () menjadi items () dan PEP-3113 yang menghapus parameter Tuple membongkar , dengan Python 3.x Anda harus menulis Martijn Pieters ♦ jawab seperti ini:

my_dictionary = dict(map(lambda item: (item[0], f(item[1])), my_dictionary.items()))
Lucidyan
sumber
4

Sementara jawaban asli saya melewatkan intinya (dengan mencoba memecahkan masalah ini dengan solusi untuk mengakses kunci di pabrik defaultdict ), saya telah mengolahnya untuk mengusulkan solusi aktual untuk pertanyaan ini.

Ini dia:

class walkableDict(dict):
  def walk(self, callback):
    try:
      for key in self:
        self[key] = callback(self[key])
    except TypeError:
      return False
    return True

Pemakaian:

>>> d = walkableDict({ k1: v1, k2: v2 ... })
>>> d.walk(f)

Idenya adalah untuk membuat subklas dari dikt asli untuk memberikan fungsionalitas yang diinginkan: "memetakan" fungsi di atas semua nilai.

Titik plus adalah bahwa kamus ini dapat digunakan untuk menyimpan data asli seolah-olah itu adalah dict, sambil mengubah data apa pun berdasarkan permintaan dengan panggilan balik.

Tentu saja, silakan beri nama kelas dan fungsi seperti yang Anda inginkan (nama yang dipilih dalam jawaban ini terinspirasi oleh array_walk()fungsi PHP ).

Catatan: Baik try- exceptblok maupun returnpernyataan wajib untuk fungsi, mereka ada di sana untuk lebih meniru perilaku PHP array_walk.

7heo.tk
sumber
1
Ini gagal untuk menyelesaikan pertanyaan OP karena __missing__metode ini tidak akan dipanggil untuk kunci yang ada, yang ingin kita ubah, kecuali metode pabrik yang lewat menggunakan entah bagaimana dict sebagai fallback, tetapi karena itu bukan bagian dari contoh penggunaan, Saya menganggap ini sebagai jawaban yang tidak memuaskan untuk masalah yang dihadapi.
Kaos
Kunci mana yang ada?
7heo.tk
Dari OP: Given a dictionary { k1: v1, k2: v2 ... } .... Artinya, Anda sudah memiliki dictuntuk memulai dengan ..
Kaos
Saya ingin mengatakan bahwa kami berdua benar; tapi saya yakin kita berdua salah. Anda benar karena jawaban saya tidak menjawab pertanyaan; tetapi tidak untuk alasan Anda memohon. Saya hanya melewatkan intinya, memberi cara untuk mendapatkan {v1: f(v1), v2: f(v2), ...}diberikan [v1, v2, ...], dan tidak diberi dict. Saya akan mengedit jawaban saya untuk memperbaikinya.
7heo.tk
2

Untuk menghindari pengindeksan dari dalam lambda, seperti:

rval = dict(map(lambda kv : (kv[0], ' '.join(kv[1])), rval.iteritems()))

Anda juga dapat melakukan:

rval = dict(map(lambda(k,v) : (k, ' '.join(v)), rval.iteritems()))
Hormat kami
sumber
Itu adalah manipulasi cerdas dalam 2-tuple itu sendiri dalam contoh kedua. Namun, ia menggunakan tuple otomatis membongkar di dalam lambda, yang tidak lagi didukung dalam Python 3. Oleh karena itu lambda(k,v)tidak akan berfungsi. Lihat stackoverflow.com/questions/21892989/...
Jonathan Komar
0

Baru saja menemukan kasus penggunaan ini. Saya menerapkan jawaban gens , menambahkan pendekatan rekursif untuk menangani nilai-nilai yang juga merupakan dikte:

def mutate_dict_in_place(f, d):
    for k, v in d.iteritems():
        if isinstance(v, dict):
            mutate_dict_in_place(f, v)
        else:
            d[k] = f(v)

# Exemple handy usage
def utf8_everywhere(d):
    mutate_dict_in_place((
        lambda value:
            value.decode('utf-8')
            if isinstance(value, bytes)
            else value
        ),
        d
    )

my_dict = {'a': b'byte1', 'b': {'c': b'byte2', 'd': b'byte3'}}
utf8_everywhere(my_dict)
print(my_dict)

Ini bisa berguna ketika berhadapan dengan file json atau yaml yang menyandikan string sebagai byte dalam Python 2

Oyono
sumber