เหตุใดรายการจึงไม่มีวิธี "รับ" ที่ปลอดภัยเช่นพจนานุกรม


264

เหตุใดรายการจึงไม่มีวิธี "รับ" ที่ปลอดภัยเช่นพจนานุกรม

>>> d = {'a':'b'}
>>> d['a']
'b'
>>> d['c']
KeyError: 'c'
>>> d.get('c', 'fail')
'fail'

>>> l = [1]
>>> l[10]
IndexError: list index out of range

1
รายการใช้เพื่อวัตถุประสงค์ที่แตกต่างจากพจนานุกรม ไม่จำเป็นต้องใช้ get () สำหรับกรณีการใช้งานทั่วไปของรายการ อย่างไรก็ตามสำหรับพจนานุกรม get () ค่อนข้างมีประโยชน์บ่อยครั้ง
mgronber

42
คุณสามารถได้รับรายการย่อยที่ว่างเปล่าออกจากรายการโดยไม่ต้องเพิ่ม IndexError ถ้าคุณถามหาชิ้นแทน: l[10:11]แทนl[10]ตัวอย่างเช่น () รายการย่อย Th จะมีองค์ประกอบที่ต้องการถ้ามี)
jsbueno

56
.getตรงกันข้ามกับบางอย่างที่นี่ผมสนับสนุนความคิดของความปลอดภัยที่ มันจะเทียบเท่าl[i] if i < len(l) else defaultแต่อ่านได้ง่ายขึ้นกระชับมากขึ้นและอนุญาตให้iมีการแสดงออกโดยไม่ต้องคำนวณใหม่
Paul Draper

6
วันนี้ฉันต้องการสิ่งนี้มีอยู่ ฉันใช้ฟังก์ชั่นราคาแพงที่คืนค่ารายการ แต่ฉันต้องการเฉพาะรายการแรกหรือNoneหากไม่มีอยู่ คงจะเป็นการดีที่จะพูดx = expensive().get(0, None)ดังนั้นฉันจะไม่ต้องใส่ผลตอบแทนที่ไร้ประโยชน์ของราคาแพงลงในตัวแปรชั่วคราว
Ryan Hiebert

2
@ Ryan คำตอบของฉันอาจช่วยให้คุณstackoverflow.com/a/23003811/246265
Jake

คำตอบ:


112

ในที่สุดมันก็อาจจะไม่มี.getวิธีที่ปลอดภัยเพราะ a dictเป็นคอลเลกชันที่เชื่อมโยง (ค่าเชื่อมโยงกับชื่อ) ซึ่งมันไม่มีประสิทธิภาพในการตรวจสอบว่ามีกุญแจอยู่หรือไม่และส่งกลับค่าของมันโดยไม่ทิ้งข้อยกเว้น เพื่อหลีกเลี่ยงข้อยกเว้นในการเข้าถึงองค์ประกอบรายการ (เนื่องจากlenวิธีนี้เร็วมาก) .getวิธีช่วยให้คุณสามารถสอบถามค่าที่เกี่ยวข้องกับชื่อไม่ได้โดยตรงเข้าถึงรายการที่ 37 ในพจนานุกรม (ซึ่งจะเป็นเหมือนสิ่งที่คุณขอของรายการของคุณ)

แน่นอนคุณสามารถใช้สิ่งนี้ได้อย่างง่ายดาย:

def safe_list_get (l, idx, default):
  try:
    return l[idx]
  except IndexError:
    return default

คุณอาจจะลิงมันลงบนตัว__builtins__.listสร้างใน__main__แต่มันจะเป็นการเปลี่ยนแปลงที่แพร่หลายน้อยกว่าเนื่องจากโค้ดส่วนใหญ่ไม่ได้ใช้ หากคุณต้องการใช้สิ่งนี้กับรายการที่สร้างขึ้นโดยรหัสของคุณเองคุณสามารถ subclass listและเพิ่มgetวิธีการ


24
Python ไม่อนุญาตให้ใช้งานประเภทลิงในการจับลิงlist
Imran

7
@CSZ: .getแก้ปัญหาที่ไม่มีรายการ - วิธีที่มีประสิทธิภาพในการหลีกเลี่ยงข้อยกเว้นเมื่อรับข้อมูลที่อาจไม่มีอยู่ เป็นเรื่องเล็กน้อยและมีประสิทธิภาพมากในการทราบว่าดัชนีรายการที่ถูกต้องคืออะไร แต่ไม่มีวิธีที่ดีในการทำเช่นนี้สำหรับค่าคีย์ในพจนานุกรม
Nick Bastin

10
ฉันไม่คิดว่านี่จะเกี่ยวกับประสิทธิภาพเลย - ตรวจสอบว่ามีคีย์อยู่ในพจนานุกรมและ / หรือส่งคืนรายการO(1)หรือไม่ มันจะไม่เป็นที่ค่อนข้างเป็นไปอย่างรวดเร็วในแง่ดิบตรวจสอบแต่จากจุดที่ซับซ้อนในมุมมองของพวกเขากำลังทั้งหมดlen O(1)คำตอบที่ถูกต้องคือการใช้งานทั่วไป / ซีแมนทิกส์หนึ่ง ๆ ...
มาร์คลองแอร์

3
@ Mark: ไม่ได้สร้าง O ทั้งหมด (1) เท่ากัน นอกจากนี้dictเป็นกรณีที่ดีที่สุดเท่านั้น O (1) ไม่ใช่ทุกกรณี
Nick Bastin

4
ฉันคิดว่าคนหายไปตรงจุดนี้ การอภิปรายไม่ควรเกี่ยวกับประสิทธิภาพ โปรดหยุดด้วยการเพิ่มประสิทธิภาพก่อนเวลาอันควร หากโปรแกรมของคุณช้าเกินไปแสดงว่าคุณกำลังดูถูกเหยียดหยาม.get()หรือมีปัญหาอื่น ๆ ในรหัสของคุณ (หรือสภาพแวดล้อม) จุดประสงค์ของการใช้วิธีนี้คือการอ่านรหัสได้ เทคนิค "วานิลลา" ต้องใช้รหัสสี่บรรทัดในทุกที่ที่ต้องทำ .get()เทคนิคเพียง แต่ต้องใช้อย่างใดอย่างหนึ่งและสามารถจะถูกล่ามโซ่ด้วยวิธีการโทรตามมา (เช่นmy_list.get(2, '').uppercase())
Tyler Crompton

67

มันจะทำงานถ้าคุณต้องการองค์ประกอบแรกเช่น my_list.get(0)

>>> my_list = [1,2,3]
>>> next(iter(my_list), 'fail')
1
>>> my_list = []
>>> next(iter(my_list), 'fail')
'fail'

ฉันรู้ว่าไม่ใช่สิ่งที่คุณขอ แต่อาจช่วยคนอื่นได้


7
pythonic น้อยกว่าการเขียนโปรแกรมแบบ esque
Eric

next(iter(my_list[index:index+1]), 'fail')ช่วยให้ดัชนีใด ๆ ที่ไม่เพียง 0. หรือน้อย FP แต่เนื้อหาเพิ่มเติม Pythonic my_list[index] if index < len(my_list) else 'fail'และเกือบจะแน่นอนสามารถอ่านเพิ่มเติมได้ที่:
alphabetasoup

47

อาจเป็นเพราะมันไม่สมเหตุสมผลนักสำหรับความหมายของรายการ อย่างไรก็ตามคุณสามารถสร้างของคุณเองได้อย่างง่ายดายโดยการแบ่งคลาสย่อย

class safelist(list):
    def get(self, index, default=None):
        try:
            return self.__getitem__(index)
        except IndexError:
            return default

def _test():
    l = safelist(range(10))
    print l.get(20, "oops")

if __name__ == "__main__":
    _test()

5
นี่คือการตอบรับแบบ pythonic ที่มากที่สุดโดย OP โปรดทราบว่าคุณสามารถแตกรายการย่อยซึ่งเป็นการทำงานที่ปลอดภัยใน Python ระบุ mylist = [1, 2, 3] คุณสามารถลองแยกองค์ประกอบที่ 9 ด้วยรายการของฉัน [8: 9] โดยไม่มีการกระตุ้นข้อยกเว้น จากนั้นคุณสามารถทดสอบว่ารายการว่างเปล่าและในกรณีที่ไม่ว่างเปล่าให้แยกองค์ประกอบเดียวจากรายการที่ส่งคืน
jose.angel.jimenez

1
นี่ควรเป็นคำตอบที่ได้รับการยอมรับไม่ใช่แฮ็คแบบซับไพ ธ อนแบบอื่นที่ไม่ใช่ pythonic โดยเฉพาะอย่างยิ่งเพราะมันช่วยรักษาความสมมาตรกับพจนานุกรม
Eric

1
ไม่มีอะไรที่ไพทอนเกี่ยวกับการจัดคลาสย่อยรายการของคุณเองเพียงเพราะคุณต้องการgetวิธีการที่ดี จำนวนการอ่าน และความสามารถในการอ่านนั้นเพิ่มขึ้นทุกคลาสที่ไม่จำเป็น เพียงใช้try / exceptวิธีการนี้โดยไม่ต้องสร้างคลาสย่อย
Jeyekomon

@ Jeyekomon มันเป็น Pythonic ที่สมบูรณ์แบบในการลดความร้อนด้วยการซับคลาส
Keith

42

แทนที่จะใช้. get การใช้สิ่งนี้ควรเป็นรายการ เพียงแค่ความแตกต่างในการใช้งาน

>>> l = [1]
>>> l[10] if 10 < len(l) else 'fail'
'fail'

15
สิ่งนี้ล้มเหลวหากเราพยายามรับองค์ประกอบล่าสุดด้วย -1
pretobomba

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

แบบอินไลน์ถ้า / อื่นใช้ไม่ได้กับไพ ธ อนแบบเก่าเช่น 2.6 (หรือมันคือ 2.5?)
Eric

3
@TylerCrompton: ไม่มีรายการที่เชื่อมโยงแบบวงกลมในไพ ธ อน หากคุณเขียนด้วยตัวคุณเองคุณมีอิสระที่จะใช้.getวิธีการ (ยกเว้นฉันไม่แน่ใจว่าคุณจะอธิบายได้อย่างไรว่าดัชนีมีความหมายอย่างไรในกรณีนี้หรือทำไมมันถึงล้มเหลว)
Nick Bastin

ทางเลือกที่จัดการดัชนีลบนอกขอบเขตได้คือlst[i] if -len(lst) <= i < len(l) else 'fail'
ไมค์


15

มอบเครดิตให้กับjose.angel.jimenez


สำหรับแฟน ๆ "oneliner" ...


หากคุณต้องการองค์ประกอบแรกของรายการหรือถ้าคุณต้องการค่าเริ่มต้นหากรายการว่างเปล่าลอง:

liste = ['a', 'b', 'c']
value = (liste[0:1] or ('default',))[0]
print(value)

ผลตอบแทน a

และ

liste = []
value = (liste[0:1] or ('default',))[0]
print(value)

ผลตอบแทน default


ตัวอย่างสำหรับองค์ประกอบอื่น ๆ ...

liste = ['a', 'b', 'c']
print(liste[0:1])  # returns ['a']
print(liste[1:2])  # returns ['b']
print(liste[2:3])  # returns ['c']

ด้วยทางเลือกเริ่มต้น ...

liste = ['a', 'b', 'c']
print((liste[0:1] or ('default',))[0])  # returns a
print((liste[1:2] or ('default',))[0])  # returns b
print((liste[2:3] or ('default',))[0])  # returns c

ทดสอบกับ Python 3.6.0 (v3.6.0:41df79263a11, Dec 22 2016, 17:23:13)


1
ทางเลือกสั้น ๆ : value, = liste[:1] or ('default',). ดูเหมือนว่าคุณต้องการวงเล็บ
qräbnö

13

สิ่งที่ดีที่สุดที่คุณสามารถทำได้คือการแปลงรายการเป็น dict แล้วเข้าถึงด้วยวิธี get:

>>> my_list = ['a', 'b', 'c', 'd', 'e']
>>> my_dict = dict(enumerate(my_list))
>>> print my_dict
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}
>>> my_dict.get(2)
'c'
>>> my_dict.get(10, 'N/A')

20
วิธีแก้ปัญหาที่สมเหตุสมผล แต่แทบจะไม่ "สิ่งที่ดีที่สุดที่คุณสามารถทำได้"
tripleee

3
แม้ว่าจะไม่มีประสิทธิภาพมาก หมายเหตุ: แทนที่จะzip range lenเป็นอย่างนั้นใคร ๆ ก็ใช้ได้dict(enumerate(my_list))
Marian

3
นี่ไม่ใช่สิ่งที่ดีที่สุดมันเป็นสิ่งที่แย่ที่สุดที่คุณสามารถทำได้
erikbwork

3
มันเป็นสิ่งที่แย่ที่สุดถ้าคุณพิจารณาประสิทธิภาพ ... ถ้าคุณใส่ใจกับประสิทธิภาพคุณไม่ต้องเขียนโค้ดในภาษาที่ตีความเช่นหลาม ฉันพบโซลูชันนี้โดยใช้พจนานุกรมค่อนข้างหรูหรามีประสิทธิภาพและไพเราะ การปรับให้เหมาะสมในช่วงต้นนั้นเป็นสิ่งที่ชั่วร้ายดังนั้นเราจะมีพจน์และดูทีหลังว่ามันเป็นคอขวด
Eric

7

ดังนั้นฉันจึงทำการวิจัยเพิ่มเติมเกี่ยวกับเรื่องนี้และปรากฎว่าไม่มีอะไรพิเศษสำหรับเรื่องนี้ ฉันรู้สึกตื่นเต้นเมื่อพบ list.index (ค่า) มันจะส่งคืนดัชนีของรายการที่ระบุ แต่ไม่มีอะไรที่จะได้รับค่าที่ดัชนีเฉพาะ ดังนั้นหากคุณไม่ต้องการใช้โซลูชัน safe_list_get ซึ่งฉันคิดว่าค่อนข้างดี ต่อไปนี้เป็นตัวอย่างการใช้งาน 1 รายการหากข้อความที่สามารถทำให้งานสำเร็จขึ้นอยู่กับสถานการณ์:

>>> x = [1, 2, 3]
>>> el = x[4] if len(x) > 4 else 'No'
>>> el
'No'

คุณยังสามารถใช้ไม่มีแทน 'ไม่' ซึ่งเหมาะสมกว่า:

>>> x = [1, 2, 3]
>>> i = 2
>>> el_i = x[i] if len(x) == i+1 else None

นอกจากนี้หากคุณต้องการรับไอเท็มแรกหรือไอเท็มสุดท้ายในรายการการทำงานนี้

end_el = x[-1] if x else None

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

def list_get(l, i):
    try:
        return l[i]
    except IndexError:
        return None

ยังไม่ได้ทำการเปรียบเทียบเพื่อดูว่าอะไรเร็วที่สุด


1
ไม่ได้เป็น pythonic จริงๆ
Eric

@Eric ตัวอย่างข้อมูลใด ฉันคิดว่าลองยกเว้นทำให้เหมาะสมที่สุดโดยดูอีกครั้ง
radtek

ฟังก์ชั่นสแตนด์อโลนไม่ได้เป็น pythonic ข้อยกเว้นนั้นเป็นเรื่องเล็กมาก แต่ไม่มากนักเนื่องจากเป็นรูปแบบทั่วไปในภาษาการเขียนโปรแกรม pythonic มีอะไรเพิ่มเติมคือวัตถุใหม่ที่ขยายประเภท builtin listโดย subclassing มัน วิธีการที่สร้างสามารถใช้หรือสิ่งที่ชอบทำตัวรายการและพฤติกรรมของตัวอย่างใหม่เช่นเดียวกับlist listดูคำตอบของ Keith ด้านล่างซึ่งควรเป็น IMHO ที่ยอมรับได้
Eric

1
@Eric ฉันแยกวิเคราะห์คำถามไม่ใช่เฉพาะ OOP แต่เป็น "เหตุใดรายการจึงไม่มีการเปรียบเทียบเพื่อdict.get()ส่งคืนค่าเริ่มต้นจากการอ้างอิงดัชนีรายการแทนที่จะต้องจับได้IndexErrorหรือไม่ดังนั้นจึงเป็นเรื่องเกี่ยวกับภาษา / ไลบรารี (ไม่ใช่ OOP . เมื่อเทียบกับ FP บริบท) นอกจากนี้อาจจะมีหนึ่งที่จะมีคุณสมบัติการใช้งานของ 'pythonic' เป็นบางที WWGD (ตามที่ดูถูกของเขาสำหรับ FP งูใหญ่ที่รู้จักกันดี) และไม่จำเป็นต้องเป็นเพียงแค่ความพึงพอใจ PEP8 / 20.
cowbert

1
el = x[4] if len(x) == 4 else 'No'- คุณหมายถึงlen(x) > 4อะไร คือออกจากขอบเขตถ้าx[4] len(x) == 4
ไมค์

4

พจนานุกรมใช้สำหรับการค้นหา มันสมเหตุสมผลที่จะถามว่ามีรายการหรือไม่ รายการมักจะซ้ำแล้วซ้ำอีก ไม่ใช่เรื่องธรรมดาที่จะถามว่ามี L [10] อยู่หรือไม่ แต่ถ้าความยาวของ L คือ 11


ใช่เห็นด้วยกับคุณ แต่ฉันแยกวิเคราะห์ URL ที่สัมพันธ์กันของหน้า "/ group / Page_name" แยกโดย '/' และต้องการตรวจสอบว่า PageName เท่ากับหน้าบาง มันจะสะดวกที่จะเขียนอะไรบางอย่างเช่น [url.split ('/') get_from_index (2, None) == "lalala"] แทนที่จะทำการตรวจสอบความยาวพิเศษหรือจับข้อยกเว้นหรือฟังก์ชั่นการเขียนของตัวเอง บางทีคุณคิดถูกก็ถือว่าผิดปกติ อย่างไรก็ตามฉันยังไม่เห็นด้วยกับเรื่องนี้ =)
CSZ

@Nick Bastin: ไม่มีอะไรผิดปกติ มันคือทั้งหมดที่เกี่ยวกับความเรียบง่ายและความเร็วของการเข้ารหัส
CSZ

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

-1

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

คุณสามารถพูดแบบนี้: ฉันต้องการ. get () ในพจนานุกรมค่อนข้างบ่อย หลังจากสิบปีในฐานะโปรแกรมเมอร์เต็มเวลาฉันไม่คิดว่าฉันต้องการมันในรายการ :)


ตัวอย่างของฉันเกี่ยวกับความคิดเห็นเป็นอย่างไร อะไรที่ง่ายและอ่านได้มากขึ้น? (url.split ('/'). getFromIndex (2) == "lalala") หรือ (result = url.split (); len (ผลลัพธ์)> 2 และผลลัพธ์ [2] == "lalala") และใช่ฉันรู้ว่าฉันสามารถเขียนฟังก์ชั่นดังกล่าวได้ด้วยตัวเอง =) แต่ฉันก็ประหลาดใจที่ฟังก์ชั่นดังกล่าวไม่ได้ถูกสร้างขึ้น
CSZ

1
รหัสพูดในกรณีของคุณว่าคุณทำผิด การจัดการ URL ควรทำโดยเส้นทาง (การจับคู่รูปแบบ) หรือการข้ามวัตถุ 'lalala' in url.split('/')[2:]แต่จะตอบเฉพาะกรณีของคุณ: แต่ปัญหาของการแก้ปัญหาของคุณตรงนี้คือคุณมองที่องค์ประกอบที่สองเท่านั้น เกิดอะไรขึ้นถ้า URL คือ '/ monkeybonkey / lalala' คุณจะได้รับTrueแม้ว่า URL จะไม่ถูกต้อง
Lennart Regebro

ฉันเอาองค์ประกอบที่สองเท่านั้นเพราะฉันต้องการองค์ประกอบที่สองเท่านั้น แต่ใช่ชิ้นส่วนดูเหมือนจะเป็นทางเลือกที่ดีในการทำงาน
CSZ

@CSZ: แต่องค์ประกอบแรกจะถูกละเว้นและในกรณีนี้คุณสามารถข้ามมันได้ :) ดูว่าฉันหมายถึงอะไรตัวอย่างนั้นใช้งานไม่ได้ในชีวิตจริง
Lennart Regebro
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.