ใช้กรณีสำหรับวิธีการ 'setdefault' dict


192

นอกจากนี้collections.defaultdictในหลาม 2.5 ลดลงอย่างมากความจำเป็นในการdict's setdefaultวิธี คำถามนี้สำหรับการศึกษาโดยรวมของเรา:

  1. setdefaultยังมีประโยชน์อะไรในปัจจุบันใน Python 2.6 / 2.7?
  2. กรณีการใช้งานที่เป็นที่นิยมของอะไรsetdefaultถูกแทนที่ด้วยcollections.defaultdict?

1
มีความเกี่ยวข้องเล็กน้อยstackoverflow.com/questions/7423428/…
ผู้ใช้

คำตอบ:


208

คุณอาจจะบอกว่าdefaultdictจะเป็นประโยชน์สำหรับค่าเริ่มต้นของการตั้งค่าก่อนที่จะกรอก Dictและsetdefaultเป็นประโยชน์สำหรับการตั้งค่าเริ่มต้นในขณะหรือหลังจากกรอกบริการพจนานุกรม

อาจเป็นกรณีการใช้งานที่พบบ่อยที่สุด: การจัดกลุ่มรายการ (ในข้อมูลที่ไม่ได้เรียงลำดับใช้อย่างอื่น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

บางครั้งคุณต้องการให้แน่ใจว่ามีคีย์เฉพาะอยู่หลังจากสร้าง dict defaultdictไม่สามารถใช้งานได้ในกรณีนี้เพราะจะสร้างคีย์เฉพาะในการเข้าถึงอย่างชัดเจน คิดว่าคุณใช้ HTTP-ish กับส่วนหัวจำนวนมาก - บางตัวเลือก แต่คุณต้องการค่าเริ่มต้นสำหรับพวกเขา:

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 )

1
อันที่จริง IMHO defaultdictนี้เป็นกรณีที่ใช้สำหรับการเปลี่ยนหัวหน้าโดย คุณสามารถยกตัวอย่างสิ่งที่คุณหมายถึงในวรรคแรก?
Eli Bendersky

2
Muhammad Alkarouri: สิ่งที่คุณทำอันดับแรกคือการคัดลอก dict จากนั้นเขียนทับบางรายการ setdefaultฉันทำที่มากเกินไปและฉันเดาว่าเป็นจริงสำนวนส่วนใหญ่ชอบมากกว่า defaultdictในมืออื่น ๆ จะไม่ทำงานหากไม่ได้ทั้งหมดที่defaultvaluesมีค่าเท่ากัน (เช่นบางคน0และบางคน[])
Jochen Ritzel

2
@ YHC4k ใช่ headers = dict(optional_headers)นั่นคือเหตุผลที่ผมใช้ สำหรับกรณีที่ค่าเริ่มต้นไม่เท่ากันทั้งหมด และผลลัพธ์สุดท้ายนั้นเหมือนกับว่าคุณได้รับส่วนหัว HTTP ก่อนจากนั้นตั้งค่าเริ่มต้นสำหรับสิ่งที่คุณไม่ได้รับ optional_headersและมันค่อนข้างใช้งานได้หากคุณมีอยู่แล้ว ลองใช้โค้ด 2 ขั้นตอนของฉันแล้วเปรียบเทียบกับของคุณแล้วคุณจะเห็นว่าฉันหมายถึงอะไร
Muhammad Alkarouri

19
หรือเพียงแค่ทำnew.setdefault(key, []).append(value)
fmalina

2
ฉันคิดว่ามันแปลกที่คำตอบที่ดีที่สุดจะdefaultdictยิ่งดีกว่าsetdefault(ตอนนี้กรณีการใช้งานอยู่ที่ไหน) นอกจากนี้ChainMapควรจัดการกับhttpตัวอย่าง IMO
YvesgereY

29

ฉันมักจะใช้setdefaultสำหรับการโต้แย้งคำหลักเช่นในฟังก์ชั่นนี้:

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)

มันยอดเยี่ยมสำหรับการปรับแต่งอาร์กิวเมนต์ในการล้อมรอบฟังก์ชันที่รับอาร์กิวเมนต์ของคำหลัก


16

defaultdict ดีมากเมื่อค่าเริ่มต้นเป็นแบบคงที่เช่นรายการใหม่ แต่ไม่มากถ้าเป็นแบบไดนามิก

ตัวอย่างเช่นฉันต้องการพจนานุกรมเพื่อจับคู่สตริงกับ ints ที่ไม่ซ้ำกัน defaultdict(int)จะใช้ 0 เป็นค่าเริ่มต้นเสมอ เช่นเดียวกันdefaultdict(intGen())สร้าง 1 เสมอ

แต่ฉันใช้ dict ปกติ:

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

โปรดทราบว่าdict.get(key, nextID())ไม่เพียงพอเพราะฉันต้องสามารถอ้างอิงค่าเหล่านี้ได้ในภายหลังเช่นกัน

intGen เป็นคลาสเล็ก ๆ ที่ฉันสร้างขึ้นซึ่งจะเพิ่มค่า int โดยอัตโนมัติและส่งกลับค่าของมัน:

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

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

หากใครมีวิธีการทำเช่นนี้กับdefaultdictฉันชอบที่จะเห็นมัน


สำหรับวิธีที่จะทำกับ defaultdict (คลาสย่อย) ดูคำถามนี้: stackoverflow.com/questions/2912231/…
weronika

8
คุณสามารถแทนที่ด้วยintGen itertools.count().next
พลวง

7
nextID()ค่า 's เป็นไปได้เพิ่มขึ้นทุกครั้งที่ถูกเรียกว่าแม้ว่าค่ามันกลับไม่ได้ถูกนำมาใช้เป็นmyDict.setdefault() strIDดูเหมือนว่าจะสิ้นเปลืองและแสดงให้เห็นถึงสิ่งหนึ่งที่ฉันไม่ชอบsetdefault()โดยทั่วไปนั่นคือมันประเมินการdefaultโต้แย้งเสมอว่าจะใช้จริงหรือไม่
martineau

คุณสามารถทำมันได้ด้วย:defaultdict myDict = defaultdict(lambda: nextID())ต่อมาstrID = myDict[myStr]ในวง
musiphil

3
เพื่อให้ได้พฤติกรรมที่คุณอธิบายด้วยค่าเริ่มต้นทำไมไม่เพียงแค่นั้น myDict = defaultdict(nextID) ?
forty_two

10

ผมใช้เมื่อฉันต้องการค่าเริ่มต้นในsetdefault() OrderedDictไม่มีคอลเลกชันหลามมาตรฐานที่ไม่ทั้งสอง แต่มีมี วิธีการที่จะดำเนินการเช่นคอลเลกชัน


10

เนื่องจากคำตอบส่วนใหญ่จะระบุsetdefaultหรือdefaultdictให้คุณตั้งค่าเริ่มต้นเมื่อไม่มีคีย์ แต่ผมอยากจะชี้ให้เห็นคำเตือนเล็ก ๆ setdefaultเกี่ยวกับกรณีการใช้งานด้วย เมื่อ Python interpreter ดำเนินการsetdefaultมันจะประเมินอาร์กิวเมนต์ที่สองของฟังก์ชันเสมอแม้ว่าจะมีคีย์อยู่ในพจนานุกรมก็ตาม ตัวอย่างเช่น:

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

อย่างที่คุณเห็นมันprintก็ถูกประหารชีวิตแม้ว่าจะมี 2 อยู่ในพจนานุกรมแล้ว นี้กลายเป็นสิ่งสำคัญโดยเฉพาะอย่างยิ่งถ้าคุณกำลังวางแผนที่จะใช้ตัวอย่างเช่นสำหรับการเพิ่มประสิทธิภาพเช่นsetdefault memoizationหากคุณเพิ่มการเรียกฟังก์ชันแบบเรียกซ้ำเป็นอาร์กิวเมนต์ที่สองsetdefaultคุณจะไม่ได้รับประสิทธิภาพใด ๆ เนื่องจาก Python จะเรียกใช้ฟังก์ชันแบบเรียกซ้ำเสมอ

เนื่องจากมีการกล่าวถึงการบันทึกช่วยจำทางเลือกที่ดีกว่าคือการใช้ functools.lru_cache มัณฑนากรหากคุณพิจารณาเพิ่มฟังก์ชั่นด้วยการบันทึก lru_cache จัดการกับข้อกำหนดการแคชสำหรับฟังก์ชันเรียกซ้ำได้ดีขึ้น


8

ดังที่มูฮัมหมัดกล่าวว่ามีบางสถานการณ์ที่บางครั้งคุณเพียงต้องการตั้งค่าเริ่มต้น ตัวอย่างที่ดีของสิ่งนี้คือโครงสร้างข้อมูลซึ่งมีการเติมข้อมูลครั้งแรกจากนั้นสอบถาม

พิจารณาคู่ชีวิต เมื่อเพิ่มคำหากจำเป็นต้องใช้โหนดย่อย แต่ไม่มีอยู่ต้องสร้างคำสั่งเพื่อขยายทั้งคู่ เมื่อทำการสอบถามถึงการปรากฏตัวของคำสั่งโหนดย่อยที่ขาดหายแสดงว่าคำนั้นไม่มีอยู่และไม่ควรสร้างขึ้นมา

Defaultdict ไม่สามารถทำได้ ต้องใช้ dict ปกติพร้อมกับเมธอด get และ setdefault แทน


5

ในทางทฤษฎีการพูดsetdefaultจะเป็นประโยชน์ถ้าคุณบางครั้งต้องการตั้งค่าเริ่มต้นและบางครั้งก็ไม่ ในชีวิตจริงฉันไม่ได้เจอกรณีการใช้งานแบบนี้

อย่างไรก็ตามกรณีการใช้งานที่น่าสนใจเกิดขึ้นจากไลบรารีมาตรฐาน (Python 2.6, _threadinglocal.py):

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

ฉันจะบอกว่าใช้ __dict__.setdefaultเป็นกรณีที่มีประโยชน์มาก

แก้ไข : มันเกิดขึ้นนี่เป็นเพียงตัวอย่างเดียวในไลบรารีมาตรฐานและมันอยู่ในความคิดเห็น ดังนั้นอาจจะไม่เพียงพอที่จะแสดงให้เห็นถึงการดำรงอยู่ของsetdefaultดังนั้นอาจจะไม่เพียงพอของกรณีที่จะปรับการดำรงอยู่ของยังนี่คือคำอธิบาย:

วัตถุเก็บคุณสมบัติของพวกเขาใน__dict__คุณลักษณะ มันเกิดขึ้นได้__dict__ทุกเมื่อหลังจากการสร้างวัตถุ defaultdictนอกจากนี้ยังเป็นพจนานุกรมไม่ได้เป็น มันไม่สมเหตุสมผลสำหรับวัตถุในกรณีทั่วไปที่จะ__dict__เป็นdefaultdictเพราะจะทำให้แต่ละวัตถุมีตัวระบุทางกฎหมายทั้งหมดเป็นคุณลักษณะ ดังนั้นฉันจึงไม่สามารถคาดการณ์การเปลี่ยนแปลงใด ๆ กับวัตถุ Python ที่จะกำจัดได้__dict__.setdefaultนอกจากจะลบมันโดยสิ้นเชิงถ้ามันไม่ได้มีประโยชน์อะไร


1
คุณช่วยอธิบายรายละเอียดได้อย่างไร - อะไรทำให้_dict .setdefault มีประโยชน์เป็นพิเศษ
Eli Bendersky

1
@Eli: ผมคิดว่าประเด็นก็คือว่า__dict__โดยการดำเนินงานไม่ได้dict defaultdict
Katriel

1
Alright ฉันไม่สนใจที่จะsetdefaultอยู่ใน Python แต่อยากรู้ว่าตอนนี้มันไร้ประโยชน์แล้ว
Eli Bendersky

@Eli: ฉันเห็นด้วย ฉันไม่คิดว่าจะมีเหตุผลเพียงพอที่จะเปิดตัวในวันนี้หากไม่มี แต่ถ้าอยู่ที่นั่นแล้วมันคงเป็นการยากที่จะโต้แย้งว่าจะลบมันออกไปเพราะให้ใช้โค้ดทั้งหมดแล้ว
Muhammad Alkarouri

1
ไฟล์ภายใต้การตั้งโปรแกรมป้องกัน setdefaultทำให้ชัดเจนว่าคุณจะมอบหมายให้ Dict ผ่านที่สำคัญที่อาจจะหรืออาจจะไม่อยู่และถ้ามันไม่ได้อยู่ที่คุณต้องการสร้างขึ้นด้วยค่าเริ่มต้น: d.setdefault(key,[]).append(value)ยกตัวอย่างเช่น ที่อื่นในโปรแกรมที่คุณทำalist=d[k]โดยที่ k ถูกคำนวณและคุณต้องการให้เกิดข้อยกเว้นถ้า k ไม่ได้อยู่ใน d (ซึ่งด้วย defaultdict อาจต้องใช้assert k in dหรือแม้กระทั่งif not ( k in d): raise KeyError
nigel222

3

หนึ่งในข้อเสียเปรียบของdefaultdictเกินdict( dict.setdefault) คือdefaultdictวัตถุที่สร้างรายการใหม่ทุกครั้งที่ไม่ได้รับคีย์ที่มีอยู่ (เช่นกับ==, print) นอกจากนี้defaultdictคลาสโดยทั่วไปแล้วจะมีวิธีการเรียนที่น้อยกว่าdictซึ่งเป็นเรื่องยากที่จะทำให้เป็นลำดับ IME

ฟังก์ชั่น PS IMO | วิธีการที่ไม่ได้หมายถึงการกลายพันธุ์วัตถุไม่ควรกลายพันธุ์วัตถุ


ไม่จำเป็นต้องสร้างวัตถุใหม่ทุกครั้ง คุณสามารถทำได้อย่างง่ายดายdefaultdict(lambda l=[]: l)แทน
Artyer

6
อย่าทำสิ่งที่ @Artyer แนะนำ - ค่าเริ่มต้นที่ไม่แน่นอนจะกัดคุณ
Brandon Humpert

2

นี่คือตัวอย่างของ setdefault ที่จะแสดงประโยชน์:

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

2

ฉันเขียนคำตอบที่ได้รับการยอมรับใหม่อีกครั้งและง่ายสำหรับมือใหม่

#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 []

นอกจากนี้ฉันจัดหมวดหมู่วิธีการอ้างอิง:

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

1

ฉันใช้ setdefault บ่อยครั้งเมื่อรับสิ่งนี้ตั้งค่าเริ่มต้น (!!!) ในพจนานุกรม ค่อนข้างทั่วไปพจนานุกรม os.environ:

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

รัดกุมน้อยกว่านี้มีลักษณะเช่นนี้:

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

เป็นที่น่าสังเกตว่าคุณสามารถใช้ตัวแปรผลลัพธ์:

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

แต่นั่นเป็นสิ่งที่จำเป็นน้อยกว่าก่อนที่จะมีค่าเริ่มต้น


1

อีกกรณีการใช้งานที่ฉันไม่คิดว่าถูกกล่าวถึงข้างต้น บางครั้งคุณเก็บแคชตามคำสั่งของวัตถุโดย id ของพวกเขาที่อินสแตนซ์หลักอยู่ในแคชและคุณต้องการตั้งค่าแคชเมื่อหายไป

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

สิ่งนี้มีประโยชน์เมื่อคุณต้องการเก็บอินสแตนซ์เดียวต่อ id ที่แตกต่างกันเสมอไม่ว่าคุณจะได้รับ obj ในแต่ละครั้ง ตัวอย่างเช่นเมื่อแอตทริบิวต์ของวัตถุได้รับการปรับปรุงในหน่วยความจำและการบันทึกไปยังที่เก็บข้อมูลถูกเลื่อนออกไป


1

กรณีการใช้งานที่สำคัญอย่างหนึ่งที่ฉันเพิ่งเจอ: dict.setdefault()เหมาะสำหรับโค้ดแบบมัลติเธรดเมื่อคุณต้องการเพียงอ็อบเจกต์มาตรฐานเดียว (ซึ่งตรงข้ามกับหลาย ๆ อ็อบเจ็กต์ที่เท่ากัน)

ตัวอย่างเช่น(Int)FlagEnum ใน Python 3.6.0 มีข้อบกพร่อง : หากมีหลายเธรดที่แข่งขันกันสำหรับ(Int)Flagสมาชิกคอมโพสิตอาจมีมากกว่าหนึ่งหัวข้อ:

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)

ทางออกคือการใช้setdefault()เป็นขั้นตอนสุดท้ายของการบันทึกสมาชิกคอมโพสิตที่คำนวณ - ถ้ามีคนอื่นได้รับการบันทึกแล้วก็จะถูกนำมาใช้แทนสมาชิกใหม่รับประกันสมาชิก Enum ที่ไม่ซ้ำกัน


0

[แก้ไข] ผิดมาก!setdefault จะเรียก long_computation เสมอ Python กระตือรือร้น

ขยายคำตอบของ Tuttle สำหรับฉันกรณีการใช้งานที่ดีที่สุดคือกลไกแคช แทน:

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

ซึ่งกินเวลา 3 บรรทัดและการค้นหา 2 หรือ 3 ครั้งฉันจะเขียนอย่างมีความสุข :

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

ตัวอย่างที่ดี ฉันยังคงคิดว่า 3 บรรทัดนั้นเข้าใจได้มากกว่า แต่บางทีสมองของฉันจะโตขึ้นเพื่อชื่นชม setdefault
Bob Stein

5
สิ่งเหล่านั้นไม่เท่ากัน ในครั้งแรกที่เป็นเพียงการเรียกว่าถ้าlong_computation(x) x not in memoในขณะที่ในสองlong_computation(x)เรียกเสมอ เท่านั้นที่ได้รับมอบหมายเป็นเงื่อนไขรหัสเทียบเท่ากับจะมีลักษณะ:setdefault / / v = long_computation(x)if x not in memo:memo[x] = v
Dan D.

0

ฉันชอบคำตอบที่ได้รับที่นี่:

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

ในระยะสั้นการตัดสินใจ (ในแอพที่ไม่สำคัญต่อประสิทธิภาพ) ควรทำบนพื้นฐานของวิธีที่คุณต้องการจัดการกับการค้นหาคีย์ว่างดาวน์สตรีม ( ได้แก่ KeyErrorเทียบกับค่าเริ่มต้น)


0

กรณีการใช้ที่แตกต่างกันsetdefault()คือเมื่อคุณไม่ต้องการเขียนทับค่าของคีย์ที่ตั้งค่าไว้แล้ว defaultdictเขียนทับในขณะที่setdefault()ไม่ สำหรับพจนานุกรมที่ซ้อนกันบ่อยครั้งที่คุณต้องการตั้งค่าเริ่มต้นเฉพาะเมื่อยังไม่ได้ตั้งค่าคีย์เนื่องจากคุณไม่ต้องการลบพจนานุกรมย่อยปัจจุบัน นี่คือเมื่อคุณใช้setdefault()นี้คือเมื่อคุณใช้

ตัวอย่างด้วยdefaultdict:

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

setdefault ไม่ได้เขียนทับ:

>>> bar = dict()
>>> bar.setdefault('a', 4)
>>> bar.setdefault('a', 2)
>>> print(bar)
{'a': 4}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.