Gebruik voorbeelden voor de ‘setdefault’ dict-methode

De toevoeging van collections.defaultdictin Python 2.5 heeft de behoefte aan dict‘s setdefault-methode aanzienlijk verminderd. Deze vraag is voor ons collectief onderwijs:

  1. Waar is setdefaultnog steeds nuttig voor, vandaag in Python 2.6/2.7?
  2. Welke populaire use-cases van setdefaultzijn vervangen door collections.defaultdict?

Antwoord 1, autoriteit 100%

Je zou kunnen zeggen dat defaultdicthandig is voor het instellen van standaardwaarden voordat het dictaat wordt ingevulden setdefaultis handig voor het instellen van standaardwaarden terwijl of erna het dictaat invullen.

Waarschijnlijk het meest voorkomende gebruik: items groeperen (in ongesorteerde gegevens, gebruik anders 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

Soms wil je er zeker van zijn dat specifieke sleutels bestaan ​​nadat je een dictaat hebt gemaakt. defaultdictwerkt in dit geval niet, omdat het alleen sleutels maakt bij expliciete toegang. Denk dat je iets HTTP-achtigs gebruikt met veel headers — sommige zijn optioneel, maar je wilt er standaardwaarden voor:

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 )

Antwoord 2, autoriteit 15%

Ik gebruik vaak setdefaultvoor trefwoordargumenten, zoals in deze functie:

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)

Het is geweldig voor het aanpassen van argumenten in wrappers rond functies die trefwoordargumenten gebruiken.


Antwoord 3, autoriteit 8%

defaultdictis geweldig als de standaardwaarde statisch is, zoals een nieuwe lijst, maar niet zozeer als deze dynamisch is.

Ik heb bijvoorbeeld een woordenboek nodig om strings toe te wijzen aan unieke ints. defaultdict(int)gebruikt altijd 0 voor de standaardwaarde. Evenzo produceert defaultdict(intGen())altijd 1.

In plaats daarvan gebruikte ik een gewoon dictaat:

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

Merk op dat dict.get(key, nextID())onvoldoende is omdat ik later ook naar deze waarden moet kunnen verwijzen.

intGenis een kleine klasse die ik bouw die automatisch een int verhoogt en de waarde ervan teruggeeft:

class intGen:
    def __init__(self):
        self.i = 0
    def __call__(self):
        self.i += 1
    return self.i

Als iemand een manier heeft om dit te doen met defaultdict, zou ik dat graag zien.


4, Autoriteit 5%

Ik gebruik setdefault()wanneer ik een standaardwaarde wil in een OrderedDict. Er is geen standaard Python-collectie die zowel, maar er zijn manieren om een ​​dergelijke verzameling te implementeren.


5, Autoriteit 3%

Zoals Mohammed zei, zijn er situaties waarin u slechts soms een standaardwaarde wilt instellen. Een geweldig voorbeeld hiervan is een gegevensstructuur die eerst wordt bevolkt en vervolgens wordt opgevraagd.

Overweeg een trie. Bij het toevoegen van een woord, als een subnode nodig is, maar niet aanwezig, moet het worden gemaakt om de trie uit te breiden. Bij het opvragen voor de aanwezigheid van een woord, geeft een ontbrekende subnode aan dat het woord niet aanwezig is en het niet moet worden gemaakt.

een standaardicto kan dit niet doen. In plaats daarvan moet een regelmatige dict met de werk- en setDefault-methoden worden gebruikt.


Antwoord 6

Een nadeel van defaultdictten opzichte van dict(dict.setdefault) is dat een defaultdict-object een nieuw item EVERYTIMEniet bestaande sleutel wordt gegeven (bijv. met ==, print). Ook is de klasse defaultdictover het algemeen veel minder gebruikelijk dan de klasse dict, het is moeilijker om deze IME te serialiseren.

P.S. IMO-functies|methoden die niet bedoeld zijn om een ​​object te muteren, mogen een object niet muteren.


Antwoord 7

Hier zijn enkele voorbeelden van setdefault om het nut ervan aan te tonen:

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

Antwoord 8

Ik gebruik setdefault vaak wanneer, krijg dit, het instellen van een standaard (!!!) in een woordenboek; enigszins algemeen het woordenboek os.environ:

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

Minder beknopt, dit ziet er als volgt uit:

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

Het is vermeldenswaard dat u ook de resulterende variabele kunt gebruiken:

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

Maar dat is minder nodig dan voordat er standaarddictaten bestonden.


Antwoord 9

Nog een use-case waarvan ik denk dat deze hierboven niet is genoemd.
Soms houdt u een cache-dict van objecten bij op hun id waar de primaire instantie zich in de cache bevindt en wilt u cache instellen wanneer ze ontbreken.

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

Dat is handig wanneer u altijd één exemplaar per afzonderlijke id wilt behouden, ongeacht hoe u elke keer een obj verkrijgt. Bijvoorbeeld wanneer objectkenmerken in het geheugen worden bijgewerkt en het opslaan naar de opslag wordt uitgesteld.


Antwoord 10

Een zeer belangrijke use-case die ik zojuist tegenkwam: dict.setdefault()is geweldig voor multi-threaded code wanneer je slechts één canoniek object wilt (in tegenstelling tot meerdere objecten die toevallig gelijk zijn).

Bijvoorbeeld, de (Int)FlagEnum in Python 3.6.0 heeft een bug: als meerdere threads strijden om een ​​samengesteld (Int)Flag-lid, kunnen er uiteindelijk meer dan één zijn:

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)

De oplossing is om setdefault()te gebruiken als laatste stap voor het opslaan van het berekende samengestelde lid — als een ander al is opgeslagen, wordt het gebruikt in plaats van het nieuwe, waardoor unieke Enum-leden worden gegarandeerd .


Antwoord 11

[Bewerken] Heel fout!De setdefault zou altijd long_computation activeren, Python was gretig.

Uitbreiding van het antwoord van Tuttle. Voor mij is de beste use case het cachemechanisme. In plaats van:

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

die 3 regels en 2 of 3 zoekopdrachten in beslag neemt, Ik zou graag schrijven:

return memo.setdefault(x, long_computation(x))

Antwoord 12

Ik vind het antwoord dat hier wordt gegeven leuk:

http://stupidpythonideas.blogspot.com/2013/08 /defaultdict-vs-setdefault.html

Kortom, de beslissing (in niet-prestatiekritieke apps) moet worden genomen op basis van hoe u het opzoeken van lege sleutels stroomafwaarts wilt afhandelen (nl.KeyErrorversus standaardwaarde).


Antwoord 13

Het andere gebruik voor setdefault()is wanneer u de waarde van een reeds ingestelde sleutel niet wilt overschrijven. defaultdictoverschrijft, terwijl setdefault()dat niet doet. Voor geneste woordenboeken is het vaker zo dat je alleen een standaard wilt instellen als de sleutel nog niet is ingesteld, omdat je het huidige subwoordenboek niet wilt verwijderen. Dit is wanneer je setdefault()gebruikt.

Voorbeeld met defaultdict:

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

setdefaultoverschrijft niet:

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

Antwoord 14

In aanvulling op wat is gesuggereerd, kan setdefaulthandig zijn in situaties waarin u een reeds ingestelde waarde niet wilt wijzigen. Bijvoorbeeld wanneer u dubbele nummers heeft en deze als één groep wilt behandelen. Als u in dit geval een herhaalde duplicate-sleutel tegenkomt die al is ingesteld, werkt u de waarde van die sleutel niet bij. U behoudt de eerste gevonden waarde. Alsof je de herhaalde toetsen maar één keer herhaalt/bijwerkt.

Hier is een codevoorbeeld van het opnemen van de index voor de sleutels/elementen van een gesorteerde lijst:

nums = [2,2,2,2,2]
d = {}
for idx, num in enumerate(sorted(nums)):
    # This will be updated with the value/index of the of the last repeated key
    # d[num] = idx # Result (sorted_indices): [4, 4, 4, 4, 4]
    # In the case of setdefault, all encountered repeated keys won't update the key.
    # However, only the first encountered key's index will be set 
    d.setdefault(num,idx) # Result (sorted_indices): [0, 0, 0, 0, 0]
sorted_indices = [d[i] for i in nums]

Antwoord 15

Een andere usecase voor setdefaultin CPython is dat het in alle gevallen atomair is, terwijl defaultdictniet atomair zal zijn als je een standaardwaarde gebruikt die is gemaakt op basis van een lambda.

cache = {}
def get_user_roles(user_id):
    if user_id in cache:
        return cache[user_id]['roles']
    cache.setdefault(user_id, {'lock': threading.Lock()})
    with cache[user_id]['lock']:
        roles = query_roles_from_database(user_id)
        cache[user_id]['roles'] = roles

Als twee threads tegelijkertijd cache.setdefaultuitvoeren, kan slechts één van hen de standaardwaarde maken.

Als je in plaats daarvan een standaarddictaat hebt gebruikt:

cache = defaultdict(lambda: {'lock': threading.Lock()}

Dit zou resulteren in een raceconditie. In mijn voorbeeld hierboven zou de eerste thread een standaardvergrendeling kunnen maken en de tweede thread zou een andere standaardvergrendeling kunnen maken, en dan zou elke thread zijn eigen standaardvergrendeling kunnen vergrendelen, in plaats van het gewenste resultaat van elke thread die probeert een enkele vergrendeling te vergrendelen.


Conceptueel gedraagt ​​setdefaultzich in principe als volgt (defaultdict gedraagt ​​zich ook zo als je een lege lijst, lege dict, int of een andere standaardwaarde gebruikt die geen gebruikerspython-code is zoals een lambda):

gil = threading.Lock()
def setdefault(dict, key, value_func):
    with gil:
        if key not in dict:
            return
        value = value_func()
        dict[key] = value

Conceptueel gedraagt ​​defaultdictzich in principe als volgt (alleen bij gebruik van python-code als een lambda – dit is niet waar als je een lege lijst gebruikt):

gil = threading.Lock()
def __setitem__(dict, key, value_func):
    with gil:
        if key not in dict:
            return
    value = value_func()
    with gil:
        dict[key] = value

Other episodes