มีวิธีที่ชาญฉลาดในการส่งคีย์ไปยัง default_factory ของ defaultdict หรือไม่?


95

คลาสมีตัวสร้างซึ่งรับพารามิเตอร์หนึ่งตัว:

class C(object):
    def __init__(self, v):
        self.v = v
        ...

ที่ใดที่หนึ่งในโค้ดจะมีประโยชน์สำหรับค่าใน dict เพื่อให้ทราบคีย์
ฉันต้องการใช้ defaultdict กับคีย์ที่ส่งไปยังค่าเริ่มต้นแรกเกิด:

d = defaultdict(lambda : C(here_i_wish_the_key_to_be))

ข้อเสนอแนะใด ๆ ?

คำตอบ:


128

แทบจะไม่ถือว่าฉลาดเลย - แต่คลาสย่อยเป็นเพื่อนของคุณ:

class keydefaultdict(defaultdict):
    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError( key )
        else:
            ret = self[key] = self.default_factory(key)
            return ret

d = keydefaultdict(C)
d[x] # returns C(x)

16
นั่นคือสิ่งที่น่าเกลียดที่ฉันพยายามหลีกเลี่ยง ... แม้แต่การใช้คำสั่งธรรมดา ๆ และการตรวจสอบการมีอยู่ของคีย์ก็สะอาดกว่า
Benjamin Nitlehoo

1
@ พอล: แต่นี่คือคำตอบของคุณ อัปลักษณ์? มาเลย!
tzot

4
ฉันคิดว่าฉันจะใช้รหัสนั้นและใส่ไว้ในโมดูลยูทิลิตี้ทั่วไปส่วนบุคคลของฉันเพื่อให้ฉันสามารถใช้งานได้ทุกเมื่อที่ต้องการ ไม่น่าเกลียดเกินไปแบบนั้น ...
weronika

24
+1 ตอบคำถามของ OP ได้โดยตรงและดูไม่ "น่าเกลียด" สำหรับฉัน นอกจากนี้ยังมีคำตอบที่ดีเพราะหลายคนดูเหมือนจะไม่ตระหนักว่าdefaultdict's __missing__()วิธีสามารถแทนที่ (เท่าที่จะสามารถอยู่ในประเภทรองใด ๆ ในตัวdictชั้นตั้งแต่รุ่น 2.5)
martineau

7
+1 จุดประสงค์ทั้งหมดของ __missing__ คือการปรับแต่งพฤติกรรมสำหรับคีย์ที่หายไป วิธีการ dict.setdefault () ที่กล่าวถึงโดย @silentghost ก็ใช้งานได้เช่นกัน (ในด้านบวก setdefault () สั้นและมีอยู่แล้วในด้านลบจะมีปัญหาด้านประสิทธิภาพและไม่มีใครชอบชื่อ "setdefault") .
Raymond Hettinger

26

ไม่มีไม่มี

defaultdictการดำเนินงานไม่สามารถกำหนดค่าที่จะผ่านหายkeyไปdefault_factoryออกจากกล่อง ทางเลือกเดียวของคุณคือติดตั้งdefaultdictคลาสย่อยของคุณเองตามที่ @JochenRitzel แนะนำไว้ข้างต้น

แต่นั่นไม่ได้ "ฉลาด" หรือเกือบจะสะอาดเท่ากับโซลูชันไลบรารีมาตรฐาน (ถ้ามี) ดังนั้นคำตอบของคุณอย่างรวบรัดคำถามใช่ / ไม่ใช่คือ "ไม่" อย่างชัดเจน

มันแย่เกินไปที่ไลบรารีมาตรฐานไม่มีเครื่องมือที่จำเป็นบ่อยๆเช่นนี้


ใช่มันน่าจะเป็นทางเลือกในการออกแบบที่ดีกว่าที่จะให้โรงงานใช้คีย์ (ฟังก์ชันยูนารีแทนที่จะเป็นค่าว่าง) เป็นเรื่องง่ายที่จะละทิ้งอาร์กิวเมนต์เมื่อเราต้องการคืนค่าคงที่
YvesgereY

6

ฉันไม่คิดว่าคุณต้องการdefaultdictที่นี่เลย ทำไมไม่ใช้dict.setdefaultวิธีการ?

>>> d = {}
>>> d.setdefault('p', C('p')).v
'p'

แน่นอนว่าจะสร้างอินสแตนซ์Cมากมาย ในกรณีที่เป็นปัญหาฉันคิดว่าแนวทางที่ง่ายกว่าจะทำ:

>>> d = {}
>>> if 'e' not in d: d['e'] = C('e')

มันจะเร็วกว่าdefaultdictทางเลือกอื่น ๆ เท่าที่ฉันเห็น

ETAเกี่ยวกับความเร็วของinการทดสอบเทียบกับการใช้ประโยค try-except:

>>> def g():
    d = {}
    if 'a' in d:
        return d['a']


>>> timeit.timeit(g)
0.19638929363557622
>>> def f():
    d = {}
    try:
        return d['a']
    except KeyError:
        return


>>> timeit.timeit(f)
0.6167065411074759
>>> def k():
    d = {'a': 2}
    if 'a' in d:
        return d['a']


>>> timeit.timeit(k)
0.30074866358404506
>>> def p():
    d = {'a': 2}
    try:
        return d['a']
    except KeyError:
        return


>>> timeit.timeit(p)
0.28588609450770264

7
สิ่งนี้สิ้นเปลืองอย่างมากในกรณีที่มีการเข้าถึง d หลายครั้งและแทบจะไม่มีคีย์เท่านั้น: C (คีย์) จะสร้างอ็อบเจ็กต์ที่ไม่จำเป็นจำนวนมากเพื่อให้ GC รวบรวม นอกจากนี้ในกรณีของฉันมีความเจ็บปวดเพิ่มเติมเนื่องจากการสร้างวัตถุ C ใหม่นั้นช้า
Benjamin Nitlehoo

@ พอล: ถูกต้อง ฉันขอแนะนำวิธีการที่ง่ายกว่านั้นดูการแก้ไขของฉัน
SilentGhost

ฉันไม่แน่ใจว่ามันเร็วกว่า defaultdict แต่นี่คือสิ่งที่ฉันมักจะทำ (ดูความคิดเห็นของฉันต่อคำตอบของ THC4k) ฉันหวังว่าจะมีวิธีง่ายๆในการแฮ็คโดยที่ default_factory ไม่ต้องใช้ args เพื่อให้โค้ดดูหรูหราขึ้นเล็กน้อย
Benjamin Nitlehoo

5
@SilentGhost: ฉันไม่เข้าใจ - วิธีนี้ช่วยแก้ปัญหาของ OP ได้อย่างไร ฉันคิดว่า OP ต้องการความพยายามที่จะอ่านใด ๆd[key]ที่จะกลับมาถ้าd[key] = C(key) key not in dแต่ทางแก้ของคุณต้องการให้เขาไปตั้งค่าd[key]ล่วงหน้าจริงหรือ? เขาจะรู้ได้อย่างไรว่าkeyต้องการอะไร?
สูงสุด

2
เนื่องจาก setdefault นั้นน่าเกลียดเหมือนนรกและ defaultdict จากคอลเลกชันควรสนับสนุนฟังก์ชันจากโรงงานที่ได้รับคีย์ ช่างเป็นโอกาสที่สูญเปล่าจากนักออกแบบ Python!
jgomo3

0

นี่คือตัวอย่างการทำงานของพจนานุกรมที่เพิ่มค่าโดยอัตโนมัติ งานสาธิตในการค้นหาไฟล์ที่ซ้ำกันใน / usr / include โปรดสังเกตว่าPathDictปรับแต่งพจนานุกรมต้องใช้สี่บรรทัดเท่านั้น:

class FullPaths:

    def __init__(self,filename):
        self.filename = filename
        self.paths = set()

    def record_path(self,path):
        self.paths.add(path)

class PathDict(dict):

    def __missing__(self, key):
        ret = self[key] = FullPaths(key)
        return ret

if __name__ == "__main__":
    pathdict = PathDict()
    for root, _, files in os.walk('/usr/include'):
        for f in files:
            path = os.path.join(root,f)
            pathdict[f].record_path(path)
    for fullpath in pathdict.values():
        if len(fullpath.paths) > 1:
            print("{} located in {}".format(fullpath.filename,','.join(fullpath.paths)))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.