Mengapa python dict.update () tidak mengembalikan objek?

146

Saya mencoba melakukan:

award_dict = {
    "url" : "http://facebook.com",
    "imageurl" : "http://farm4.static.flickr.com/3431/3939267074_feb9eb19b1_o.png",
    "count" : 1,
}

def award(name, count, points, desc_string, my_size, parent) :
    if my_size > count :
        a = {
            "name" : name,
            "description" : desc_string % count,
            "points" : points,
            "parent_award" : parent,
        }
        a.update(award_dict)
        return self.add_award(a, siteAlias, alias).award

Tetapi jika dirasa sangat merepotkan dalam fungsinya, dan saya lebih suka melakukannya:

        return self.add_award({
            "name" : name,
            "description" : desc_string % count,
            "points" : points,
            "parent_award" : parent,
        }.update(award_dict), siteAlias, alias).award

Mengapa tidak memperbarui mengembalikan objek sehingga Anda dapat merantai?

JQuery melakukan ini untuk melakukan perangkaian. Mengapa itu tidak dapat diterima di python?

Paul Tarjan
sumber
17
* TL; DRnewdict = dict(dict001, **dict002)
dreftymac
2
@dreftymac, itu tidak berfungsi dalam pemahaman.
alancalvitti
@alancalvitti Ya, itu memang salah satu peringatan yang valid untuk ditunjukkan.
dreftymac

Jawaban:

227

Python sebagian besar menerapkan rasa pemisahan perintah-kueri yang diwarnai secara pragmatis : mutators return None(dengan pengecualian yang diinduksi secara pragmatis seperti pop;-) sehingga mereka tidak mungkin bingung dengan pengakses (dan dalam nada yang sama, tugas bukanlah ekspresi, pernyataan -pisah ekspresi itu ada, dan sebagainya).

Itu tidak berarti tidak banyak cara untuk menggabungkan berbagai hal ketika Anda benar-benar menginginkannya, misalnya, dict(a, **award_dict)membuat dikt baru seperti yang Anda tampaknya ingin .updatekembalikan - jadi mengapa tidak menggunakan ITU jika Anda merasa itu penting ?

Sunting : btw, tidak perlu, dalam kasus khusus Anda, untuk membuat asepanjang jalan, baik:

dict(name=name, description=desc % count, points=points, parent_award=parent,
     **award_dict)

membuat dikt tunggal dengan semantik yang sama persis dengan Anda a.update(award_dict)(termasuk, jika terjadi konflik, fakta bahwa entri dalam award_dictmenimpa entri yang Anda berikan secara eksplisit; untuk mendapatkan semantik lain, yaitu, memiliki entri eksplisit yang "memenangkan" konflik tersebut, lulus award_dictsebagai satu-satunya argumen posisi , sebelum kata kunci, dan kehilangan **bentuk - dict(award_dict, name=namedll).

Alex Martelli
sumber
Nah, itu akan membuat kamus lain setelah saya harus membuat. Saya ingin membuat dict, lalu menambahkan sekumpulan nilai lain, lalu memberikannya ke sebuah fungsi.
Paul Tarjan
@Paul, dan itulah yang Anda lakukan - dengan dua pernyataan (jauh lebih mudah dibaca daripada yang Anda inginkan) yang bagi Anda "terasa sangat tidak praktis". Mengedit jawaban saya untuk menunjukkan bagaimana menghindari membuat asama sekali, btw,
Alex Martelli
2
Solusi asli tidak kuat. Jika award_dict berisi kunci yang sudah ditentukan, SyntaxError akan dilemparkan untuk argumen kata kunci berulang. solusi jamylak dict (itertools.chain (d1.iteritems (), .. d <n> .iteritems ())) tidak hanya berfungsi dalam kasus di mana kamus memiliki kunci duplikat tetapi juga dengan mudah memungkinkan Anda untuk menggabungkan beberapa kamus dengan dicts nanti di rantai yang diutamakan untuk nilai akhir.
Matt
2
Juga, jika kunci dalam award_dict bukan string, penerjemah akan melemparTypeError
kunl
3
dict(old_dict, old_key=new_value)tidak akan membuang beberapa nilai untuk kata kunci dan mengembalikan dikt baru.
Charmy
36

API Python, berdasarkan konvensi, membedakan antara prosedur dan fungsi. Fungsi menghitung nilai baru dari parameternya (termasuk objek target); prosedur memodifikasi objek dan tidak mengembalikan apa pun (yaitu mengembalikan None). Jadi prosedur memiliki efek samping, fungsi tidak. update adalah sebuah prosedur, oleh karena itu ia tidak mengembalikan nilai.

Motivasi untuk melakukannya dengan cara itu adalah bahwa jika tidak, Anda mungkin mendapatkan efek samping yang tidak diinginkan. Mempertimbangkan

bar = foo.reverse()

Jika reverse (yang membalikkan daftar di tempat) juga akan mengembalikan daftar, pengguna mungkin berpikir bahwa reverse mengembalikan daftar baru yang ditugaskan ke bar, dan tidak pernah memperhatikan bahwa foo juga akan diubah. Dengan melakukan reverse return None, mereka segera mengenali bahwa bar bukanlah hasil pembalikan, dan akan melihat lebih dekat apa efek dari kebalikannya.

Martin v. Löwis
sumber
1
Terima kasih. Mengapa tidak sebaliknya juga memberikan pilihan untuk tidak melakukannya di tempat? Performa? melakukan reverse(foo)terasa aneh.
Paul Tarjan
Menambahkan opsi tidak akan tepat: itu akan mengubah sifat metode tergantung pada parameter. Namun, metode harus benar-benar memiliki tipe pengembalian tetap (sayangnya, ada kasus di mana aturan ini dilanggar). Sangat mudah untuk membuat salinan yang dikembalikan: cukup buat salinan (menggunakan bar=foo[:]), lalu kembalikan salinannya.
Martin v. Löwis
3
Saya pikir alasannya adalah alasan yang jelas. Di bar = foo.reverse(), Anda bisa berpikir itu footidak diubah. Untuk menghindari kebingungan, Anda memiliki foo.reverse()dan bar = reversed(foo).
Roberto Bonvallet
Apa yang salah dengan mengubah sifat parameter berdasarkan parameter?
Julien
24

Ini semudah:

(lambda d: d.update(dict2) or d)(d1)
Kostya Goloveshko
sumber
15
>>> dict_merge = lambda a,b: a.update(b) or a
>>> dict_merge({'a':1, 'b':3},{'c':5})
{'a': 1, 'c': 5, 'b': 3}

Perhatikan bahwa selain mengembalikan dikt yang digabungkan, itu mengubah parameter pertama di tempat. Jadi dict_merge (a, b) akan mengubah file.

Atau, tentu saja, Anda dapat melakukan semuanya secara inline:

>>> (lambda a,b: a.update(b) or a)({'a':1, 'b':3},{'c':5})
{'a': 1, 'c': 5, 'b': 3}
Crispin Wellington
sumber
10
-1 lambdatidak boleh digunakan seperti itu, sebagai gantinya gunakan fungsi konvensional defsebagai gantinya
jamylak
8
Bahkan tidak perlu lambda, cukup gunakana.update(b) or a
Pycz
13

reputasi tidak cukup untuk komentar tersisa di jawaban atas

@beardc ini sepertinya bukan hal CPython. PyPy memberi saya "TypeError: kata kunci harus berupa string"

Solusi dengan **kwargshanya berfungsi karena kamus yang akan digabungkan hanya memiliki kunci bertipe string .

yaitu

>>> dict({1:2}, **{3:4})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings

vs.

>>> dict({1:2}, **{'3':4})
{1: 2, '3': 4}
Stephan Scheller
sumber
5

Bukannya itu tidak dapat diterima, melainkan itu dictstidak diterapkan seperti itu.

Jika Anda melihat ORM Django, ini membuat penggunaan rangkaian secara ekstensif. Ini tidak berkecil hati, Anda bahkan dapat mewarisi dari dictdan hanya menimpa updateuntuk melakukan pembaruan dan return self, jika Anda benar-benar menginginkannya.

class myDict(dict):
    def update(self, *args):
        dict.update(self, *args)
        return self
Esteban Küber
sumber
Terima kasih, ini bisa menambal dict, saya hanya ingin tahu mengapa dict () tidak mengizinkan fungsionalitas ini sendiri (karena itu semudah yang Anda tunjukkan). Apakah tambalan Django menentukan seperti ini?
Paul Tarjan
2

sedekat mungkin dengan solusi yang Anda usulkan

from collections import ChainMap

return self.add_award(ChainMap(award_dict, {
    "name" : name,
    "description" : desc_string % count,
    "points" : points,
    "parent_award" : parent,
}), siteAlias, alias).award
Matus
sumber
1
import itertools
dict_merge = lambda *args: dict(itertools.chain(*[d.iteritems() for d in args]))
Matt
sumber
1

Bagi mereka yang datang terlambat ke pesta, saya telah menyusun beberapa waktu bersama (Py 3.7), menunjukkan bahwa .update()metode berbasis terlihat sedikit (~ 5%) lebih cepat ketika input disimpan dan terasa (~ 30%) lebih cepat ketika hanya memperbarui di tempat .

Seperti biasa, semua tolok ukur harus diambil dengan sebutir garam.

def join2(dict1, dict2, inplace=False):
    result = dict1 if inplace else dict1.copy()
    result.update(dict2)
    return result


def join(*items):
    iter_items = iter(items)
    result = next(iter_items).copy()
    for item in iter_items:
        result.update(item)
    return result


def update_or(dict1, dict2):
    return dict1.update(dict2) or dict1


d1 = {i: str(i) for i in range(1000000)}
d2 = {str(i): i for i in range(1000000)}

%timeit join2(d1, d2)
# 258 ms ± 1.47 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit join(d1, d2)
# 262 ms ± 2.97 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit dict(d1, **d2)
# 267 ms ± 2.74 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit {**d1, **d2}
# 267 ms ± 1.84 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Pengaturan waktu untuk operasi di tempat sedikit lebih rumit, sehingga perlu dimodifikasi sepanjang operasi penyalinan tambahan (pengaturan waktu pertama hanya untuk referensi):

%timeit dd = d1.copy()
# 44.9 ms ± 495 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit dd = d1.copy(); join2(dd, d2)
# 296 ms ± 2.05 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit dd = d1.copy(); join2(dd, d2, True)
# 234 ms ± 1.02 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit dd = d1.copy(); update_or(dd, d2)
# 235 ms ± 1.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
norok2
sumber
0

Baru saja mencoba ini sendiri dengan Python 3.4 (jadi tidak dapat menggunakan fancy {**dict_1, **dict_2} sintaksis ).

Saya ingin memiliki kunci non-string dalam kamus serta menyediakan kamus dalam jumlah yang sewenang-wenang.

Juga, saya ingin membuat kamus baru jadi saya memilih untuk tidak menggunakan collections.ChainMap(semacam alasan awalnya saya tidak ingin menggunakan dict.update.

Inilah yang akhirnya saya tulis:

def merge_dicts(*dicts):
    all_keys  = set(k for d in dicts for k in d.keys())
    chain_map = ChainMap(*reversed(dicts))
    return {k: chain_map[k] for k in all_keys}

merge_maps({'1': 1}, {'2': 2, '3': 3}, {'1': 4, '3': 5})
# {'1': 4, '3': 5, '2': 2}
gratisan
sumber