Neden bir python dict.update () nesneyi döndürmüyor?


139

Yapmaya çalışıyorum:

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

Ama işlevde gerçekten hantal hissettiysem ve yapmayı tercih ederdim:

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

Güncelleme neden zincirleme yapabilmek için nesneyi döndürmüyor?

JQuery bunu zincirleme yapmak için yapar. Python'da neden kabul edilmiyor?


14
* TL; DRnewdict = dict(dict001, **dict002)
dreftymac

2
@ dreftymac, bu anlamada işe yaramaz.
alancalvitti

@alancalvitti Evet, bu aslında belirtilmesi gereken geçerli bir uyarı.
19'da dreftymac

Yanıtlar:


219

Python çoğunlukla bir pragmatik çalan lezzet uygulayan oluyor komut sorgu ayrılması : mutators dönmek Nonegibi pragmatik uyarılan istisnalar dışında ( pop;-) muhtemelen erişimcilerle karıştırılmamalıdır böylece (ve aynı damarda, atama, deyim bir ifade değildir - ifade ayrımı vardır, vb.).

Bu, gerçekten istediğiniz zaman şeyleri birleştirmenin pek çok yolu olmadığı anlamına gelmez, örneğin, iade dict(a, **award_dict)edilmesini istediğiniz gibi yeni bir diksiyon yapar .update- bu yüzden gerçekten önemli olduğunu düşünüyorsanız neden bunu kullanmıyorsunuz? ?

Düzenleme : btw, özel durumunuzda, ayol boyunca oluşturmanıza gerek yok:

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

Sizinle tam olarak aynı anlambilime sahip tek bir diksiyon oluşturur a.update(award_dict)(çatışmalar durumunda, award_dictaçık bir şekilde verdiğiniz girişleri geçersiz kılma; diğer anlambilimi elde etmek, yani, bu tür çatışmaları "kazanan" açık girişlere sahip olmak, geçecek award_dicttek olarak konumsal , arg önce anahtar kelime olanlarının ve yoksun **formu - dict(award_dict, name=namevs vs).


Bunu yapmak zorunda kaldıktan sonra başka bir sözlük yaratacağım. Bir diksiyon oluşturmak ve daha sonra başka değerlerden bir demet eklemek ve sonra bir işleve vermek istedim.
Paul Tarjan

@Paul, ve tam olarak bunu yapıyorsunuz - iki ifade ile (istediğiniz iç içe yoldan çok daha okunaklı) hangi "gerçekten hantal hissettiniz". Cevabımı atamamen oluşturmaktan kaçınmak için cevabımı düzenleme , btw,
Alex Martelli

1
Orijinal çözüm sağlam değildir. Award_dict önceden belirlenmiş anahtarlar içeriyorsa, tekrarlanan bir anahtar kelime argümanı için bir SyntaxError atılacaktır. jamylak'ın çözüm diktesi (itertools.chain (d1.iteritems (), .. d <n> .iteritems ())) sadece sözlüklerin yinelenen anahtarları olduğu durumda değil, aynı zamanda birden çok sözlükü daha sonra diktelerle birleştirmenize izin verir zincir nihai değer için öncelik taşır.
Matt

2
Ayrıca, Award_dict içindeki anahtarlar string değilse, tercüman aTypeError
kunl

3
dict(old_dict, old_key=new_value)anahtar kelime için birden fazla değer atmaz ve yeni bir ifade döndürmez.
Charmy

35

Python'un API'si, kural olarak, prosedürler ve fonksiyonlar arasında ayrım yapar. Fonksiyonlar yeni değerleri parametrelerinden hesaplar (herhangi bir hedef nesne dahil); yordamlar nesneleri değiştirir ve hiçbir şey döndürmez (örn. Hiçbiri döndürmezler). Yani prosedürlerin yan etkileri var, fonksiyonlar yok. güncelleme bir prosedürdür, dolayısıyla bir değer döndürmez.

Bu şekilde yapma motivasyonu, aksi takdirde istenmeyen yan etkilere sahip olabilmenizdir. Düşünmek

bar = foo.reverse()

Reverse (listeyi yerinde tersine çevirir) de listeyi döndürürse, kullanıcılar reverse öğesinin bara atanan yeni bir liste döndürdüğünü düşünebilir ve foo'nun da değiştirildiğini asla fark etmez. Tersine dönüş Yok yaparak, çubuğun tersinmenin sonucu olmadığını hemen fark ederler ve tersin etkisinin ne kadar yakın olduğuna bakarlar.


1
Teşekkür ederim. Neden tersini de yerinde yapmama seçeneğini vermeyesiniz ki? Verim? yapmak reverse(foo)garip hissettiriyor.
Paul Tarjan

Bir seçenek eklemek uygun olmaz: bir parametreye bağlı olarak yöntemin doğasını değiştirir. Bununla birlikte, yöntemler gerçekten sabit dönüş türlerine sahip olmalıdır (ne yazık ki, bu kuralın bozulduğu durumlar vardır). Geri döndürülmüş bir kopya oluşturmak kolaydır: sadece bir kopya oluşturun (kullanarak bar=foo[:]), ardından kopyayı geri alın.
Martin / Löwis

3
Bunun nedeni açıklık olduğunu düşünüyorum. İçinde bar = foo.reverse(), bunun foodeğiştirilmediğini düşünebilirsiniz . Karışıklığı önlemek için hem foo.reverse()ve hem de bar = reversed(foo).
Roberto Bonvallet

Bir parametrenin doğasını bir parametreye göre değiştirmenin nesi yanlış?
Julien


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}

Birleştirilmiş dikseli döndürmenin yanı sıra, ilk parametreyi yerinde değiştirdiğini unutmayın. Böylece dict_merge (a, b) a'yı değiştirir.

Ya da, elbette, hepsini yerinde yapabilirsiniz:

>>> (lambda a,b: a.update(b) or a)({'a':1, 'b':3},{'c':5})
{'a': 1, 'c': 5, 'b': 3}

10
-1 lambdaböyle kullanılmamalıdır, bunun yerine geleneksel işlevi defkullanın
jamylak

8
a.update(b) or a
Lamda'ya

10

üst cevapta kalan yorum için yeterli itibar yok

@beardc bu CPython gibi görünmüyor. PyPy bana "TypeError: anahtar kelimeler dize olmalı" veriyor

Çözüm **kwargsyalnızca birleştirilecek sözlüğün yalnızca string türünde anahtarları .

yani

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

5

Kabul edilemez değil, dicts daha bu şekilde uygulanmadı.

Django'nun ORM'sine bakarsanız, zincirleme işlemlerini kapsamlı bir şekilde kullanır. Cesareti kırılmaz, hatta güncellemeyi yapmak için devralabilir dictve sadece geçersiz kılabilir updateve return selfgerçekten isterseniz.

class myDict(dict):
    def update(self, *args):
        dict.update(self, *args)
        return self

Teşekkür ederim, bu dict yama olabilir, sadece dict () neden bu işlevselliğin kendisine izin vermediğini bilmek istedim (çünkü gösterdiğiniz kadar kolay). Django yaması böyle mi diyor?
Paul Tarjan

2

önerebildiğiniz çözümlere olabildiğince yakın

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

1

Partiye geç gelenler için, bazı zamanlamaları bir araya getirdim (Py 3.7), .update()girdiler korunduğunda temel yöntemlerin biraz (~% 5) daha hızlı ve sadece yerinde güncellenirken fark edilir şekilde (~% 30) daha hızlı göründüğünü gösterdim. .

Her zamanki gibi, tüm kriterler bir tuz tanesi ile alınmalıdır.

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)

Yerinde işlemlerin zamanlamaları biraz daha karmaşıktır, bu nedenle ekstra bir kopyalama işlemi sırasında değiştirilmesi gerekir (ilk zamanlama sadece referans içindir):

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

0
import itertools
dict_merge = lambda *args: dict(itertools.chain(*[d.iteritems() for d in args]))

0

Sadece kendimi Python 3.4'te deniyordum (bu yüzden fantezi kullanamadım {**dict_1, **dict_2} sözdizimini ).

Sözlüklerde dize olmayan anahtarlara sahip olmak ve isteğe bağlı olarak sözlükler sağlamak istedim.

Ayrıca, yeni bir sözlük yapmak istedim, bu yüzden kullanmamayı collections.ChainMapseçtim ( dict.updatebaşlangıçta kullanmak istemediğimin nedeni) .

İşte sonunda yazdıklarım:

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}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.