Pythonic ใช้ความเข้าใจรายการเพื่อผลข้างเคียงหรือไม่?


109

ลองนึกถึงฟังก์ชันที่ฉันเรียกหาผลข้างเคียงไม่ใช่คืนค่า (เช่นการพิมพ์ไปที่หน้าจอการอัปเดต GUI การพิมพ์ไปยังไฟล์ ฯลฯ )

def fun_with_side_effects(x):
    ...side effects...
    return y

ตอนนี้Pythonicใช้ความเข้าใจในรายการเพื่อเรียกสิ่งนี้ว่า func หรือไม่:

[fun_with_side_effects(x) for x in y if (...conditions...)]

โปรดทราบว่าฉันไม่ได้บันทึกรายการไว้ที่ใดเลย

หรือฉันควรเรียกสิ่งนี้ว่า func แบบนี้:

for x in y:
    if (...conditions...):
        fun_with_side_effects(x)

ไหนดีกว่ากันและทำไม?


6
นี่คือเส้นเขตแดน แต่คุณอาจถูกต่อต้านมากกว่าการสนับสนุน ฉันจะนั่งอันนี้: ^)
jcomeau_ictx

6
นี่เป็นทางเลือกที่ง่าย ความสามารถในการอ่านนับ - ทำวิธีที่สอง หากคุณไม่สามารถใส่บรรทัดพิเศษ 2 เส้นบนหน้าจอของคุณให้ได้จอภาพที่ใหญ่ขึ้น :)
John La Rooy

1
ความเข้าใจในรายการนั้นไม่ชัดเจนเนื่องจากละเมิด "โจ่งแจ้งดีกว่านัย" - คุณกำลังซ่อนห่วงในโครงสร้างอื่น
Fred Foo

3
@larsmans: ถ้ามีเพียง GvR เท่านั้นที่รู้ว่าเมื่อเขานำเสนอความเข้าใจในรายการตั้งแต่แรก!
Steve Jessop

2
@larsmans สตีฟเจสซอปฉันคิดว่ามันไม่ถูกต้องที่จะเข้าใจความเข้าใจในรายการเป็นแบบวนซ้ำ มันอาจถูกนำไปใช้เป็นลูปได้ดี แต่จุดของโครงสร้างเช่นนี้คือการดำเนินการกับข้อมูลรวมในแบบคู่ขนานเชิงฟังก์ชันและ (เชิงแนวคิด) หากมีปัญหากับไวยากรณ์for ... inจะใช้ทั้งสองกรณี - นำไปสู่คำถามเช่นนี้!
ส่ง

คำตอบ:


85

มันต่อต้าน Pythonic มากที่จะทำเช่นนั้นและ Pythonista ที่เก๋า ๆ จะทำให้คุณรู้สึกแย่ รายการกลางจะถูกทิ้งไปหลังจากสร้างแล้วและอาจมีขนาดใหญ่มากและมีราคาแพงในการสร้าง


5
แล้วอะไรจะเป็นวิธีที่ยิ่งใหญ่กว่านี้?
Joachim Sauer

6
คนที่ไม่เก็บรายชื่อไว้ กล่าวคือตัวแปรบางอย่างของวิธีที่สอง (ฉันเคยรู้จักกันว่าใช้ genex forมาก่อนเพื่อกำจัดif)
Ignacio Vazquez-Abrams

6
@ Joachim Sauer: ตัวอย่างที่ 2 ข้างต้น การวนซ้ำที่เหมาะสมชัดเจนและไม่ใช่รายการเพื่อความเข้าใจ ชัดเจน. ชัดเจน. ชัดเจน.
ล็อต

31

คุณไม่ควรใช้ความเข้าใจในรายการเพราะอย่างที่มีคนบอกว่าจะสร้างรายการชั่วคราวขนาดใหญ่ที่คุณไม่ต้องการ สองวิธีต่อไปนี้เทียบเท่า:

consume(side_effects(x) for x in xs)

for x in xs:
    side_effects(x)

ด้วยคำจำกัดความของconsumeจากitertoolsหน้าคน:

def consume(iterator, n=None):
    "Advance the iterator n-steps ahead. If n is none, consume entirely."
    # Use functions that consume iterators at C speed.
    if n is None:
        # feed the entire iterator into a zero-length deque
        collections.deque(iterator, maxlen=0)
    else:
        # advance to the empty slice starting at position n
        next(islice(iterator, n, n), None)

แน่นอนว่าอย่างหลังชัดเจนและเข้าใจง่ายกว่า


@ พอล: ฉันคิดว่ามันควรจะเป็น และแน่นอนว่าคุณสามารถทำได้แม้ว่าmapอาจจะไม่ใช้งานง่ายนักหากยังไม่เคยเขียนโปรแกรมฟังก์ชันมาก่อน
Katriel

4
ไม่แน่ใจว่านี่เป็นสำนวนโดยเฉพาะ ไม่มีข้อได้เปรียบเหนือการใช้การวนซ้ำอย่างชัดเจน
Marcin

1
วิธีแก้ไขคือconsume = collections.deque(maxlen=0).extend
PaulMcG

24

ความเข้าใจในรายการมีไว้สำหรับสร้างรายการ และถ้าคุณไม่ได้สร้างรายการคุณไม่ควรทำใช้ความเข้าใจในรายการ

ดังนั้นฉันจะได้ตัวเลือกที่สองเพียงแค่วนซ้ำรายการแล้วเรียกใช้ฟังก์ชันเมื่อเงื่อนไขมีผล


6
ฉันจะไปให้ไกลกว่านั้นและระบุว่าผลข้างเคียงในความเข้าใจรายการนั้นผิดปกติไม่คาดคิดและเป็นเรื่องชั่วร้ายแม้ว่าคุณจะใช้รายการผลลัพธ์เมื่อคุณทำเสร็จแล้วก็ตาม
Mark Ransom

11

ประการที่สองจะดีกว่า

นึกถึงคนที่ต้องเข้าใจรหัสของคุณ คุณสามารถรับผลกรรมไม่ดีได้ง่ายๆด้วยประการแรก :)

คุณสามารถอยู่ตรงกลางระหว่างทั้งสองโดยใช้ตัวกรอง () ลองพิจารณาตัวอย่าง:

y=[1,2,3,4,5,6]
def func(x):
    print "call with %r"%x

for x in filter(lambda x: x>3, y):
    func(x)

10
แลมบ์ดาของคุณเขียนเป็นlambda x : x > 3ไฟล์.
PaulMcG

คุณไม่จำเป็นต้องใช้ตัวกรอง เพียงแค่ใส่แสดงออกกำเนิดใน parens for el in (x for x in y if x > 3):ที่นี่: elและxอาจมีชื่อเดียวกัน แต่อาจทำให้ผู้คนสับสนได้
Omnifarious

3

ขึ้นอยู่กับเป้าหมายของคุณ

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

หากคุณกำลังพยายามสร้างรายการจากรายการอื่นคุณอาจใช้ความเข้าใจในรายการ

Explicit ดีกว่าโดยปริยาย ง่ายดีกว่าซับซ้อน (Python Zen)



-1

การใช้ความเข้าใจในรายการสำหรับผลข้างเคียงนั้นน่าเกลียดไม่ใช่ Pythonic ไม่มีประสิทธิภาพและฉันจะไม่ทำ ฉันจะใช้forลูปแทนเพราะกforลูปส่งสัญญาณถึงรูปแบบขั้นตอนซึ่งผลข้างเคียงมีความสำคัญ

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

any(fun_with_side_effects(x) and False for x in y if (...conditions...))

หรือ:

all(fun_with_side_effects(x) or True for x in y if (...conditions...))

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

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

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


ถ้าfun_with_side_effectsส่งคืน True จะเป็นอย่างไร?
Katriel

7
ฉันคิดว่าการรักษานี้แย่กว่าโรค - itertools.consume สะอาดกว่ามาก
PaulMcG

@PaulMcG - itertools.consumeไม่มีแล้วอาจเป็นเพราะการใช้ความเข้าใจกับผลข้างเคียงเป็นสิ่งที่น่าเกลียด
Omnifarious

1
ปรากฎว่าฉันเข้าใจผิดและไม่เคยมีอยู่เป็นวิธีการใน stdlib มันเป็นสูตรในเอกสาร itertools A: docs.python.org/3/library/...
PaulMcG
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.