Gunakan case untuk metode dict 'setdefault'

192

Penambahan collections.defaultdictdi Python 2.5 sangat mengurangi kebutuhan untuk dict's setdefaultmetode. Pertanyaan ini untuk pendidikan kolektif kita:

  1. Apa yang setdefaultmasih berguna untuk hari ini di Python 2.6 / 2.7?
  2. Kasus penggunaan populer apa setdefaultyang digantikan collections.defaultdict?
Eli Bendersky
sumber
1
Sedikit terkait juga stackoverflow.com/questions/7423428/…
pengguna

Jawaban:

208

Bisa dibilang defaultdictberguna untuk pengaturan default sebelum mengisi dict dan setdefaultberguna untuk pengaturan default saat atau setelah mengisi dict .

Mungkin kasus penggunaan yang paling umum: Mengelompokkan item (dalam data yang tidak disortir, gunakan yang lain itertools.groupby )

# really verbose
new = {}
for (key, value) in data:
    if key in new:
        new[key].append( value )
    else:
        new[key] = [value]


# easy with setdefault
new = {}
for (key, value) in data:
    group = new.setdefault(key, []) # key might exist already
    group.append( value )


# even simpler with defaultdict 
from collections import defaultdict
new = defaultdict(list)
for (key, value) in data:
    new[key].append( value ) # all keys have a default already

Terkadang Anda ingin memastikan bahwa kunci tertentu ada setelah membuat dict. defaultdicttidak berfungsi dalam hal ini, karena hanya membuat kunci pada akses eksplisit. Pikirkan Anda menggunakan sesuatu HTTP-ish dengan banyak header - beberapa opsional, tetapi Anda ingin default untuk mereka:

headers = parse_headers( msg ) # parse the message, get a dict
# now add all the optional headers
for headername, defaultvalue in optional_headers:
    headers.setdefault( headername, defaultvalue )
Jochen Ritzel
sumber
1
Memang, IMHO ini adalah kasus penggunaan utama untuk penggantian oleh defaultdict. Bisakah Anda memberikan contoh tentang apa yang Anda maksud dalam paragraf pertama?
Eli Bendersky
2
Muhammad Alkarouri: Yang pertama kali Anda lakukan adalah menyalin dikt itu lalu menimpa beberapa item. Saya juga sering melakukan itu dan saya rasa itu adalah ungkapan yang paling disukai setdefault. A defaultdictdi sisi lain tidak akan berfungsi jika tidak semua defaultvaluessama (yaitu ada beberapa 0dan beberapa ada []).
Jochen Ritzel
2
@ YHC4k, ya. Itu sebabnya saya menggunakan headers = dict(optional_headers). Untuk kasus ketika nilai default tidak semuanya sama. Dan hasil akhirnya sama seperti jika Anda mendapatkan header HTTP terlebih dahulu kemudian atur default untuk yang tidak Anda dapatkan. Dan itu cukup berguna jika Anda sudah memilikinya optional_headers. Coba kode 2 langkah saya yang diberikan dan bandingkan dengan kode Anda, dan Anda akan mengerti maksud saya.
Muhammad Alkarouri
19
atau hanyanew.setdefault(key, []).append(value)
fmalina
2
Saya merasa aneh bahwa jawaban terbaik defaultdictadalah lebih baik daripada setdefault(jadi di mana use case sekarang?). Juga,ChainMap akan lebih baik menangani httpcontohnya, IMO.
YvesgereY
29

Saya biasanya menggunakan setdefaultuntuk argumen argumen kata kunci, seperti dalam fungsi ini:

def notify(self, level, *pargs, **kwargs):
    kwargs.setdefault("persist", level >= DANGER)
    self.__defcon.set(level, **kwargs)
    try:
        kwargs.setdefault("name", self.client.player_entity().name)
    except pytibia.PlayerEntityNotFound:
        pass
    return _notify(level, *pargs, **kwargs)

Ini bagus untuk mengubah argumen dalam pembungkus di sekitar fungsi yang mengambil argumen kata kunci.

Matt Joiner
sumber
16

defaultdict bagus ketika nilai defaultnya statis, seperti daftar baru, tetapi tidak terlalu banyak jika itu dinamis.

Sebagai contoh, saya perlu kamus untuk memetakan string ke int unik. defaultdict(int)akan selalu menggunakan 0 untuk nilai default. Juga,defaultdict(intGen()) selalu menghasilkan 1.

Sebagai gantinya, saya menggunakan dict biasa:

nextID = intGen()
myDict = {}
for lots of complicated stuff:
    #stuff that generates unpredictable, possibly already seen str
    strID = myDict.setdefault(myStr, nextID())

Catatan yang dict.get(key, nextID())tidak mencukupi karena saya harus dapat merujuk ke nilai-nilai ini nanti.

intGen adalah kelas kecil yang saya bangun yang secara otomatis menambah int dan mengembalikan nilainya:

class intGen:
    def __init__(self):
        self.i = 0

    def __call__(self):
        self.i += 1
    return self.i

Jika seseorang memiliki cara untuk melakukan ini dengan defaultdictsaya ingin melihatnya.

David Kanarek
sumber
untuk cara melakukannya dengan (subkelas) defaultdict, lihat pertanyaan ini: stackoverflow.com/questions/2912231/…
weronika
8
Anda bisa menggantinya intGendengan itertools.count().next.
Antimony
7
nextID()Nilai akan bertambah setiap kali myDict.setdefault()dipanggil, meskipun nilai yang dikembalikan tidak digunakan sebagai strID. Ini sepertinya boros dan menggambarkan salah satu hal yang tidak saya sukai setdefault()secara umum - yaitu selalu mengevaluasi defaultargumennya apakah benar-benar digunakan atau tidak.
martineau
Anda dapat melakukannya dengan defaultdict: myDict = defaultdict(lambda: nextID()). Nanti, strID = myDict[myStr]di loop.
musiphil
3
Untuk mendapatkan perilaku yang Anda gambarkan dengan defaultdict, mengapa tidak adil myDict = defaultdict(nextID)?
forty_two
10

Saya menggunakan setdefault()ketika saya ingin nilai default dalam sebuah OrderedDict. Tidak ada koleksi Python standar yang tidak baik, tetapi ada yang cara untuk menerapkan koleksi tersebut.

AndyGeek
sumber
9

Karena sebagian besar jawaban menyatakan setdefaultatau defaultdictakan membiarkan Anda menetapkan nilai default saat kunci tidak ada. Namun, saya ingin menunjukkan peringatan kecil sehubungan dengan kasus penggunaan setdefault. Ketika interpreter Python dijalankan, setdefaultia akan selalu mengevaluasi argumen kedua ke fungsi bahkan jika kunci ada dalam kamus Sebagai contoh:

In: d = {1:5, 2:6}

In: d
Out: {1: 5, 2: 6}

In: d.setdefault(2, 0)
Out: 6

In: d.setdefault(2, print('test'))
test
Out: 6

Seperti yang Anda lihat, printjuga dieksekusi meskipun 2 sudah ada dalam kamus. Ini menjadi sangat penting jika Anda berencana untuk menggunakan setdefaultmisalnya untuk optimasi seperti memoization. Jika Anda menambahkan pemanggilan fungsi rekursif sebagai argumen kedua setdefault, Anda tidak akan mendapatkan kinerja dari itu karena Python akan selalu memanggil fungsi secara rekursif.

Karena memoisasi disebutkan, alternatif yang lebih baik adalah menggunakan functools.lru_cache dekorator jika Anda mempertimbangkan untuk meningkatkan fungsi dengan memoisasi. lru_cache menangani persyaratan caching untuk fungsi rekursif yang lebih baik.

teman satu 涅
sumber
8

Seperti yang dikatakan Muhammad, ada situasi di mana Anda hanya kadang-kadang ingin menetapkan nilai default. Contoh yang bagus dari hal ini adalah struktur data yang pertama kali dihuni, kemudian dipertanyakan.

Pertimbangkan trie. Saat menambahkan kata, jika subnode diperlukan tetapi tidak ada, itu harus dibuat untuk memperpanjang trie. Saat menanyakan keberadaan kata, subnode yang hilang menunjukkan bahwa kata tersebut tidak ada dan tidak boleh dibuat.

Keputusan default tidak dapat melakukan ini. Sebagai gantinya, dikte reguler dengan metode get dan setdefault harus digunakan.

David Kanarek
sumber
5

Secara teoritis, setdefaultakan tetap berguna jika Anda terkadang ingin menetapkan default dan terkadang tidak. Dalam kehidupan nyata, saya belum menemukan kasus penggunaan seperti itu.

Namun, use case yang menarik muncul dari pustaka standar (Python 2.6, _threadinglocal.py):

>>> mydata = local()
>>> mydata.__dict__
{'number': 42}
>>> mydata.__dict__.setdefault('widgets', [])
[]
>>> mydata.widgets
[]

Saya akan mengatakan bahwa menggunakan __dict__.setdefaultadalah kasus yang sangat berguna.

Sunting : Ketika itu terjadi, ini adalah satu-satunya contoh di perpustakaan standar dan itu dalam komentar. Jadi mungkin itu tidak cukup kasus untuk membenarkan keberadaan setdefault. Namun, berikut ini penjelasannya:

Objek menyimpan atributnya di dalam __dict__atribut. Ketika itu terjadi, __dict__atribut dapat ditulisi kapan saja setelah pembuatan objek. Ini juga merupakan kamus, bukan a defaultdict. Hal ini tidak masuk akal untuk objek dalam kasus umum untuk memiliki __dict__sebagai defaultdictkarena itu akan membuat setiap objek memiliki semua pengidentifikasi hukum sebagai atribut. Jadi saya tidak bisa melihat adanya perubahan pada objek Python yang bisa dihilangkan __dict__.setdefault, selain menghapusnya sama sekali jika dianggap tidak berguna.

Muhammad Alkarouri
sumber
1
Bisakah Anda menguraikan - apa yang membuat _dict .setdefault sangat berguna?
Eli Bendersky
1
@ Eli: Saya pikir intinya __dict__adalah dengan implementasi a dict, bukan a defaultdict.
Katriel
1
Baik. Saya tidak keberatan setdefaulttinggal di Python, tetapi ingin tahu bahwa sekarang hampir tidak berguna.
Eli Bendersky
@Eli: Saya setuju. Saya tidak berpikir ada cukup alasan untuk diperkenalkan hari ini jika tidak ada di sana. Tetapi karena sudah ada di sana, akan sulit untuk berdebat untuk menghapusnya, mengingat semua kode sudah menggunakannya.
Muhammad Alkarouri
1
File di bawah pemrograman defensif. setdefaultmembuat eksplisit bahwa Anda menetapkan ke suatu dikt melalui kunci yang mungkin atau mungkin tidak ada, dan jika tidak ada Anda ingin itu dibuat dengan nilai default: misalnya d.setdefault(key,[]).append(value). Di tempat lain dalam program ini Anda melakukan di alist=d[k]mana k dihitung, dan Anda ingin pengecualian dilemparkan jika k di tidak dalam d (yang dengan defaultdict mungkin memerlukan assert k in datau bahkanif not ( k in d): raise KeyError
nigel222
3

Salah satu kelemahan defaultdictover dict( dict.setdefault) adalah bahwa defaultdictobjek membuat item baru SETIAP SAAT kunci yang tidak ada diberikan (misalnya dengan ==, print). Juga defaultdictkelas umumnya jauh lebih umum daripada dictkelas, lebih sulit untuk membuat serialisasi IME-nya.

Fungsi IMO PS | metode tidak dimaksudkan untuk memutasikan suatu objek, tidak boleh bermutasi suatu objek.

xged
sumber
Tidak harus membuat objek baru setiap saat. Anda bisa dengan mudah melakukannya defaultdict(lambda l=[]: l).
Artyer
6
Jangan pernah melakukan apa yang disarankan @Artyer - default yang bisa berubah akan menggigit Anda.
Brandon Humpert
2

Berikut adalah beberapa contoh setdefault untuk menunjukkan manfaatnya:

"""
d = {}
# To add a key->value pair, do the following:
d.setdefault(key, []).append(value)

# To retrieve a list of the values for a key
list_of_values = d[key]

# To remove a key->value pair is still easy, if
# you don't mind leaving empty lists behind when
# the last value for a given key is removed:
d[key].remove(value)

# Despite the empty lists, it's still possible to 
# test for the existance of values easily:
if d.has_key(key) and d[key]:
    pass # d has some values for key

# Note: Each value can exist multiple times!
"""
e = {}
print e
e.setdefault('Cars', []).append('Toyota')
print e
e.setdefault('Motorcycles', []).append('Yamaha')
print e
e.setdefault('Airplanes', []).append('Boeing')
print e
e.setdefault('Cars', []).append('Honda')
print e
e.setdefault('Cars', []).append('BMW')
print e
e.setdefault('Cars', []).append('Toyota')
print e

# NOTE: now e['Cars'] == ['Toyota', 'Honda', 'BMW', 'Toyota']
e['Cars'].remove('Toyota')
print e
# NOTE: it's still true that ('Toyota' in e['Cars'])
Stefan Gruenwald
sumber
2

Saya menulis ulang jawaban yang diterima dan memfasilitasi untuk pemula.

#break it down and understand it intuitively.
new = {}
for (key, value) in data:
    if key not in new:
        new[key] = [] # this is core of setdefault equals to new.setdefault(key, [])
        new[key].append(value)
    else:
        new[key].append(value)


# easy with setdefault
new = {}
for (key, value) in data:
    group = new.setdefault(key, []) # it is new[key] = []
    group.append(value)



# even simpler with defaultdict
new = defaultdict(list)
for (key, value) in data:
    new[key].append(value) # all keys have a default value of empty list []

Selain itu, saya mengkategorikan metode sebagai referensi:

dict_methods_11 = {
            'views':['keys', 'values', 'items'],
            'add':['update','setdefault'],
            'remove':['pop', 'popitem','clear'],
            'retrieve':['get',],
            'copy':['copy','fromkeys'],}
Kalkulus
sumber
1

Saya sering menggunakan setdefault ketika, dapatkan ini, menetapkan default (!!!) dalam kamus; agak umum kamus os.environ:

# Set the venv dir if it isn't already overridden:
os.environ.setdefault('VENV_DIR', '/my/default/path')

Kurang ringkas, ini terlihat seperti ini:

# Set the venv dir if it isn't already overridden:
if 'VENV_DIR' not in os.environ:
    os.environ['VENV_DIR'] = '/my/default/path')

Perlu dicatat bahwa Anda juga dapat menggunakan variabel yang dihasilkan:

venv_dir = os.environ.setdefault('VENV_DIR', '/my/default/path')

Tapi itu kurang penting daripada sebelum ada defaultdicts.

woodm1979
sumber
1

Kasus penggunaan lain yang menurut saya tidak disebutkan di atas. Kadang-kadang Anda menyimpan dict cache objek dengan id mereka di mana instance utama ada di cache dan Anda ingin mengatur cache ketika hilang.

return self.objects_by_id.setdefault(obj.id, obj)

Itu berguna ketika Anda selalu ingin menyimpan satu instance per id yang berbeda tidak peduli bagaimana Anda mendapatkan obj setiap kali. Misalnya ketika atribut objek diperbarui dalam memori dan penyimpanan ke penyimpanan ditangguhkan.

Tuttle
sumber
1

Satu use case yang sangat penting yang saya temukan: dict.setdefault() sangat bagus untuk kode multi-threaded ketika Anda hanya ingin satu objek kanonik (yang bertentangan dengan beberapa objek yang kebetulan sama).

Misalnya, (Int)FlagEnum in Python 3.6.0 memiliki bug : jika beberapa utas bersaing untuk anggota komposit (Int)Flag, mungkin ada lebih dari satu:

from enum import IntFlag, auto
import threading

class TestFlag(IntFlag):
    one = auto()
    two = auto()
    three = auto()
    four = auto()
    five = auto()
    six = auto()
    seven = auto()
    eight = auto()

    def __eq__(self, other):
        return self is other

    def __hash__(self):
        return hash(self.value)

seen = set()

class cycle_enum(threading.Thread):
    def run(self):
        for i in range(256):
            seen.add(TestFlag(i))

threads = []
for i in range(8):
    threads.append(cycle_enum())

for t in threads:
    t.start()

for t in threads:
    t.join()

len(seen)
# 272  (should be 256)

Solusinya adalah digunakan setdefault()sebagai langkah terakhir untuk menyelamatkan anggota komposit yang dihitung - jika yang lain telah disimpan maka itu digunakan sebagai pengganti yang baru, menjamin anggota Enum yang unik.

Ethan Furman
sumber
0

[Sunting] Sangat salah!Setdefault akan selalu memicu long_computation, Python bersemangat.

Memperluas jawaban Tuttle. Bagi saya kasus penggunaan terbaik adalah mekanisme cache. Dari pada:

if x not in memo:
   memo[x]=long_computation(x)
return memo[x]

yang menghabiskan 3 baris dan 2 atau 3 pencarian, saya akan dengan senang hati menulis :

return memo.setdefault(x, long_computation(x))
YvesgereY
sumber
Contoh yang baik. Saya masih berpikir 3 baris lebih mudah dipahami, tetapi mungkin otak saya akan tumbuh untuk menghargai setdefault.
Bob Stein
5
Itu tidak setara. Dalam yang pertama, long_computation(x)hanya disebut jika x not in memo. Padahal di yang kedua, long_computation(x)selalu disebut. Hanya tugas bersyarat, kode setara dengan setdefaultakan terlihat seperti: v = long_computation(x)/ if x not in memo:/ memo[x] = v.
Dan D.
0

Perbedaan use case setdefault()adalah ketika Anda tidak ingin menimpa nilai dari kunci yang telah ditetapkan. defaultdictmenimpa, sementara setdefault()tidak. Untuk kamus tersarang, lebih sering Anda ingin menetapkan default hanya jika kunci belum disetel, karena Anda tidak ingin menghapus sub kamus saat ini. Ini saat Anda menggunakansetdefault() .

Contoh dengan defaultdict:

>>> from collection import defaultdict()
>>> foo = defaultdict()
>>> foo['a'] = 4
>>> foo['a'] = 2
>>> print(foo)
defaultdict(None, {'a': 2})

setdefault tidak menimpa:

>>> bar = dict()
>>> bar.setdefault('a', 4)
>>> bar.setdefault('a', 2)
>>> print(bar)
{'a': 4}
Iodnas
sumber