วิธี Pythonic เพื่อหลีกเลี่ยงคำสั่ง“ if x: return x”


218

ฉันมีวิธีการที่เรียก 4 วิธีอื่น ๆ ตามลำดับเพื่อตรวจสอบเงื่อนไขเฉพาะและคืนค่าทันที

def check_all_conditions():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

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

x and return x

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


7
x เหล่านี้คืออะไร? พวกเขาเป็นเพียงจริง / เท็จหรือพวกเขามีโครงสร้างข้อมูลที่มีข้อมูลบางส่วนโดยไม่มีหรือคล้ายกันถูกใช้เป็นกรณีพิเศษเพื่อบ่งบอกถึงการขาดข้อมูลใด ๆ ? หากเป็นข้อหลังคุณควรใช้ข้อยกเว้นแทน
นาธาเนียล

13
@gerrit โค้ดที่นำเสนอข้างต้นเป็นรหัสสมมุติ / หลอกซึ่งอยู่นอกหัวข้อในการตรวจสอบรหัส หากผู้เขียนโพสต์ต้องการรับรหัสงานจริงจริงของพวกเขาได้รับการตรวจสอบแล้วใช่พวกเขายินดีที่จะโพสต์ในการตรวจสอบโค้ด
วลีที่

4
ทำไมคุณคิดว่าx and return xจะดีกว่าif x: return x? หลังอ่านง่ายกว่าและสามารถซ่อมบำรุงได้ คุณไม่ควรกังวลเกี่ยวกับจำนวนตัวอักษรหรือบรรทัดมากเกินไป จำนวนการอ่าน พวกเขาเป็นตัวละครที่ไม่ใช่ช่องว่างในจำนวนที่เท่ากันอยู่แล้วและถ้าคุณต้องการจริงๆif x: return xจะทำงานได้ดีในหนึ่งบรรทัด
marcelm

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

7
@ jpmc26 OP พูดถึงค่าที่ส่งคืนจริงและจากนั้นโค้ดของเขาจะส่งคืนx(ตรงข้ามกับbool(x)) ดังนั้นฉันคิดว่ามันปลอดภัยที่จะคิดว่าฟังก์ชั่นของ OP สามารถคืนสิ่งใดก็ได้และเขาต้องการสิ่งแรกที่เป็นจริง
timgeb

คำตอบ:


278

คุณสามารถใช้วง:

conditions = (check_size, check_color, check_tone, check_flavor)
for condition in conditions:
    result = condition()
    if result:
        return result

นี่เป็นข้อดีที่เพิ่มขึ้นซึ่งตอนนี้คุณสามารถกำหนดจำนวนเงื่อนไขได้แล้ว

คุณสามารถใช้map()+ filter()(เวอร์ชัน Python 3 ใช้future_builtinsเวอร์ชันใน Python 2) เพื่อรับค่าการจับคู่เช่น:

try:
    # Python 2
    from future_builtins import map, filter
except ImportError:
    # Python 3
    pass

conditions = (check_size, check_color, check_tone, check_flavor)
return next(filter(None, map(lambda f: f(), conditions)), None)

แต่ถ้าเป็นสิ่งที่อ่านได้มากกว่าจะเป็นที่ถกเถียงกัน

ตัวเลือกอื่นคือการใช้นิพจน์ตัวสร้าง:

conditions = (check_size, check_color, check_tone, check_flavor)
checks = (condition() for condition in conditions)
return next((check for check in checks if check), None)

27
หากเงื่อนไขเป็นเงื่อนไขเท่านั้นจริงๆเช่นบูลีนจากนั้นในข้อเสนอแรกของคุณคุณสามารถใช้ builtin anyแทนลูป return any(condition() for condition in conditions)

4
@ Leonhard: anyมีการใช้งานเกือบเหมือนกันภายใน แต่มันดูดีขึ้นมากโปรดโพสต์เป็นคำตอบ)
Nick Volynkin

13
ความสามารถในการอ่านสำคัญกว่าข้อควรพิจารณาอื่น ๆ เกือบทั้งหมด คุณบอกว่าแผนที่ / ตัวกรองคือ 'เป็นที่ถกเถียงกัน' ฉันใส่การลงคะแนนของฉันสำหรับสิ่งที่ไม่ดีอย่างน่าเกลียด ขอบคุณแน่นอน แต่ถ้าทุกคนในทีมของฉันใส่แผนที่ / ตัวกรองสำหรับรหัสนี้ฉันจะถ่ายโอนพวกเขาไปยังทีมอื่นหรือมอบหมายให้พวกเขาทำหน้าที่ bedpan
Kevin J. Rice

15
นี่เป็นบล็อกที่อ่านไม่ได้ของรหัส "pythonian" จริงๆหรือ? และโดยเฉพาะอย่างยิ่งความคิดของการซิปconditionsและarguments? นี่เป็น IMHO ที่แย่กว่ารหัสต้นฉบับมากซึ่งใช้เวลาประมาณ 10 วินาทีในการแยกวิเคราะห์โดยผู้ตรวจสอบสมองของฉัน
yo '

34
"ชอบงูใหญ่" พวกเขาพูด "Perl ไม่สามารถอ่านได้" พวกเขากล่าว แล้วสิ่งนี้ก็เกิดขึ้น: return next((check for check in checks if check), None).
jja

393

อีกทางเลือกหนึ่งคือคำตอบที่ดีของ Martijn orของคุณสามารถห่วงโซ่ นี่จะคืนค่าความจริงแรกหรือNoneหากไม่มีค่าจริง:

def check_all_conditions():
    return check_size() or check_color() or check_tone() or check_flavor() or None

การสาธิต:

>>> x = [] or 0 or {} or -1 or None
>>> x
-1
>>> x = [] or 0 or {} or '' or None
>>> x is None
True

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

14
@MartijnPieters คุณสามารถใช้\เพื่อวางแต่ละเช็คบนบรรทัดของตัวเอง
Caridorc

12
@MartijnPieters ฉันไม่เคยบอกเป็นนัย ๆ ว่าคำตอบของฉันดีกว่าของคุณฉันชอบคำตอบของคุณด้วยเช่นกัน :)
timgeb

38
@Caridorc: ฉันไม่ชอบการใช้\เพื่อขยายบรรทัดตรรกะ ใช้วงเล็บหากทำได้ ดังนั้นreturn (....)ด้วยการขึ้นบรรทัดใหม่ตามที่ต้องการ ถึงกระนั้นก็จะเป็นหนึ่งในสายตรรกะยาว
Martijn Pieters

47
ฉันคิดว่านี่เป็นทางออกที่ดีกว่า อาร์กิวเมนต์"มันจะได้รับน่าเบื่อ [.. ] หากมีมากกว่าสองสามตัวเลือก"เป็นที่สงสัยเพราะฟังก์ชั่นเดียวไม่ควรทำให้จำนวนมากเกินไปของการตรวจสอบต่อไป หากจำเป็นต้องมีการตรวจสอบควรแบ่งออกเป็นหลายฟังก์ชั่น
BlueRaja - Danny Pflughoeft

88

อย่าเปลี่ยนมัน

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


39
ฉันขอโต้แย้งว่า แต่ข้อเสนอแนะของคุณเป็นสิ่งที่ถูกกฎหมาย โดยส่วนตัวแล้วฉันพบว่าตาของฉันพยายามที่จะอ่าน OP ในขณะที่ตัวอย่างการแก้ปัญหาของ timgeb คลิกได้ทันที
Reti43

3
มันเป็นเรื่องของความเห็น โดยส่วนตัวแล้วฉันจะลบบรรทัดใหม่หลังจาก:นั้นเพราะฉันคิดว่าif x: return xค่อนข้างดีและมันทำให้ฟังก์ชั่นดูกะทัดรัดมากขึ้น แต่นั่นอาจเป็นเพียงฉัน
โย

2
มันไม่ใช่แค่คุณ การใช้orตามที่ timgeb ทำนั้นเป็นสำนวนที่เหมาะสมและเข้าใจดี หลายภาษามีสิ่งนี้ บางทีเมื่อมันถูกเรียกว่าorelseมันเป็นที่ชัดเจนมากยิ่งขึ้น แต่แม้ธรรมดาเก่าor(หรือ||ในภาษาอื่น ๆ ) จะหมายถึงการที่จะเข้าใจเป็นทางเลือกที่จะลองถ้าคนแรก "ไม่ทำงาน".
Ray Toal

1
@RayToal: การนำเข้าสำนวนจากภาษาอื่นเป็นวิธีที่ดีในการทำให้งงงวยรหัส
Jack Aidley

1
บางครั้งใช่แน่นอน! นอกจากนี้ยังสามารถเปิดใจและนำไปสู่การค้นพบรูปแบบและกระบวนทัศน์ใหม่ที่ดีกว่าที่ไม่มีใครเคยลองมาก่อน สไตล์วิวัฒนาการโดยผู้คนยืมและแบ่งปันและลองสิ่งใหม่ ๆ ทำงานได้ทั้งสองทาง อย่างไรก็ตามฉันไม่เคยได้ยินการใช้orun-Pythonic ที่มีป้ายกำกับหรือในทางใด ๆ ที่ทำให้งงงวย แต่นั่นเป็นเรื่องของความเห็นต่อไป --- ตามที่ควรจะเป็น
Ray Toal

83

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

def check_all_the_things():
    return (
        one()
        or two()
        or five()
        or three()
        or None
    )

8
ทุกคนช่วยกันยกคำตอบนี้ขึ้นอันดับ 1 ทำส่วนของคุณ!
Gyom

74

ตามกฎของ Curlyคุณสามารถทำให้โค้ดนี้อ่านง่ายขึ้นโดยแยกข้อกังวลสองข้อ:

  • ฉันตรวจสอบอะไรบ้าง
  • มีสิ่งหนึ่งคืนจริงหรือไม่?

เป็นสองฟังก์ชั่น:

def all_conditions():
    yield check_size()
    yield check_color()
    yield check_tone()
    yield check_flavor()

def check_all_conditions():
    for condition in all_conditions():
        if condition:
            return condition
    return None

หลีกเลี่ยงสิ่งนี้:

  • โครงสร้างเชิงตรรกะที่ซับซ้อน
  • เส้นยาวจริงๆ
  • การทำซ้ำ

... ในขณะที่รักษาลำดับการไหลให้อ่านง่าย

คุณอาจจะได้ชื่อฟังก์ชั่นที่ดีขึ้นตามสถานการณ์เฉพาะของคุณซึ่งทำให้อ่านง่ายขึ้น


ฉันชอบอันนี้ถึงแม้ว่า True / False ควรเปลี่ยนเป็นเงื่อนไข / ไม่มีเพื่อให้ตรงกับคำถาม
Malcolm

2
นี่คือสิ่งที่ฉันชอบ! มันรองรับการตรวจสอบและข้อโต้แย้งที่แตกต่างกันเช่นกัน อาจมีการออกแบบมากเกินไปสำหรับตัวอย่างนี้ แต่เป็นเครื่องมือที่มีประโยชน์สำหรับปัญหาในอนาคต!
rjh

4
โปรดทราบว่าreturn Noneไม่จำเป็นเพราะฟังก์ชั่นกลับมาNoneโดยปริยาย อย่างไรก็ตามไม่มีอะไรผิดพลาดกับการกลับมาNoneอย่างชัดเจนและฉันชอบที่คุณเลือกที่จะทำ
timgeb

1
ฉันคิดว่าวิธีนี้จะนำไปใช้ที่ดีขึ้นด้วยการกำหนดฟังก์ชั่นในท้องถิ่น
Jack Aidley

1
@timgeb "ชัดเจนดีกว่านัย" เซนของงูใหญ่
jpmc26

42

นี่คือตัวแปรของตัวอย่างแรกของ Martijns นอกจากนี้ยังใช้ "คอลเลกชัน callables" - สไตล์เพื่อให้ลัดวงจร

แทนที่จะห่วงคุณสามารถใช้ anybuiltin

conditions = (check_size, check_color, check_tone, check_flavor)
return any(condition() for condition in conditions) 

โปรดทราบว่าanyส่งคืนบูลีนดังนั้นหากคุณต้องการค่าส่งคืนที่แน่นอนของการตรวจสอบการแก้ปัญหานี้จะไม่ทำงาน anyจะไม่เห็นความแตกต่างระหว่าง14, 'red', 'sharp', เป็นค่าตอบแทนที่พวกเขาจะทั้งหมดจะกลับมาเป็น'spicy'True


คุณสามารถทำได้next(itertools.ifilter(None, (c() for c in conditions)))เพื่อให้ได้ค่าจริงโดยไม่ต้องส่งไปที่บูลีน
kojiro

1
ทำ anyจริงลัดวงจร?
zwol

1
@zwol ใช่ลองด้วยฟังก์ชั่นตัวอย่างหรือดูdocs.python.org/3/library/functions.html

1
สิ่งนี้สามารถอ่านได้น้อยกว่าการผูกมัดฟังก์ชันทั้ง 4 ด้วย 'หรือ' และจะจ่ายเฉพาะเมื่อจำนวนเงื่อนไขมีขนาดใหญ่หรือไดนามิก
rjh

1
@rjh มันอ่านได้อย่างสมบูรณ์แบบ; มันเป็นเพียงรายการตามตัวอักษรและความเข้าใจ ฉันต้องการมันเพราะดวงตาของฉันจ้องมองไปหลังจากนั้นประมาณสามx = bar(); if x: return x;
Blacklight Shining

27

คุณคิดว่าจะเขียนif x: return xทั้งหมดในบรรทัดเดียวหรือไม่?

def check_all_conditions():
    x = check_size()
    if x: return x

    x = check_color()
    if x: return x

    x = check_tone()
    if x: return x

    x = check_flavor()
    if x: return x

    return None

นี่ไม่ได้น้อยไปกว่ากัน ซ้ำซ้อนกว่าสิ่งที่คุณมี แต่ IMNSHO มันอ่านได้ค่อนข้างราบรื่นขึ้น


24

ฉันค่อนข้างประหลาดใจที่ไม่มีใครพูดถึงตัวในตัวanyซึ่งทำขึ้นเพื่อจุดประสงค์นี้:

def check_all_conditions():
    return any([
        check_size(),
        check_color(),
        check_tone(),
        check_flavor()
    ])

โปรดทราบว่าแม้ว่าการใช้งานนี้อาจเป็นสิ่งที่ชัดเจนที่สุด แต่ก็จะประเมินการตรวจสอบทั้งหมดแม้ว่าจะมีการตรวจสอบครั้งแรกTrueก็ตาม


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

def check_all_conditions():
    checks = [check_size, check_color, check_tone, check_flavor]
    return reduce(lambda a, f: a or f(), checks, False)

reduce(function, iterable[, initializer]): ใช้ฟังก์ชั่นของทั้งสองข้อโต้แย้งรวมกับรายการที่ทำซ้ำได้จากซ้ายไปขวาเพื่อลดการทำซ้ำเป็นค่าเดียว อาร์กิวเมนต์ซ้าย, x, เป็นค่าสะสมและอาร์กิวเมนต์ที่ถูกต้องคือค่าการอัพเดทจาก iterable ถ้ามี initializer ที่เป็นทางเลือกมันจะถูกวางไว้ก่อนรายการของ iterable ในการคำนวณ

ในกรณีของคุณ:

  • lambda a, f: a or f()เป็นหน้าที่ที่จะตรวจสอบว่าทั้งสะสมaหรือการตรวจสอบในปัจจุบันคือf() Trueโปรดทราบว่าถ้าaเป็นTrue, f()จะไม่ได้รับการประเมิน
  • checksมีฟังก์ชั่นตรวจสอบ ( fรายการจากแลมบ์ดา)
  • False เป็นค่าเริ่มต้นมิฉะนั้นจะไม่มีการตรวจสอบเกิดขึ้นและผลลัพธ์จะเป็นเสมอ True

anyและreduceเป็นเครื่องมือพื้นฐานสำหรับการเขียนโปรแกรมการทำงาน ฉันขอแนะนำให้คุณฝึกฝนสิ่งเหล่านี้รวมถึงสิ่งที่ยอดเยี่ยมเช่นmapกัน!


9
anyใช้ได้เฉพาะในกรณีที่เช็คส่งคืนค่าบูลีนจริง ๆTrueหรือFalseแต่คำถามไม่ได้ระบุ คุณต้องใช้reduceเพื่อคืนค่าจริงที่ส่งคืนโดยเช็ค นอกจากนี้ก็พอที่ง่ายต่อการหลีกเลี่ยงการประเมินผลการตรวจสอบทั้งหมดที่มีโดยใช้เครื่องกำเนิดไฟฟ้าเช่นany any(c() for c in (check_size, check_color, check_tone, check_flavor))เช่นเดียวกับคำตอบของ Leonhard
David Z

reduceผมชอบคำอธิบายและการใช้งานของคุณ เช่นเดียวกับ @DavidZ ผมเชื่อว่าวิธีการแก้ปัญหาของคุณด้วยanyควรใช้เครื่องกำเนิดไฟฟ้าและจะต้องมีการชี้ให้เห็นว่าจะมีข้อ จำกัด ที่จะกลับมาหรือTrue False
timgeb

1
@DavidZ ใช้anyงานได้จริงกับค่าความจริง: any([1, "abc", False]) == Trueและany(["", 0]) == False
ngasull

3
@blint ขอโทษฉันไม่ชัดเจน เป้าหมายของคำถามคือส่งคืนผลการตรวจสอบ (และไม่เพียง แต่จะระบุว่าการตรวจสอบสำเร็จหรือล้มเหลว) ฉันชี้ให้เห็นว่าanyใช้งานได้เฉพาะกับวัตถุประสงค์นั้นหากค่าบูลีนที่แท้จริงถูกส่งคืนจากฟังก์ชั่นตรวจสอบ
David Z

19

หากคุณต้องการโครงสร้างของรหัสเดียวกันคุณสามารถใช้ข้อความประกอบไปด้วย!

def check_all_conditions():
    x = check_size()
    x = x if x else check_color()
    x = x if x else check_tone()
    x = x if x else check_flavor()

    return x if x else None

ฉันคิดว่านี่ดูดีและชัดเจนถ้าคุณดู

การสาธิต:

สกรีนช็อตของมันทำงานอยู่


7
อะไรคือปลา ASCII ตัวเล็ก ๆ ที่อยู่เหนือเทอร์มินัลของคุณ?

36
@LegoStormtroopr ฉันใช้เปลือกปลาดังนั้นฉันตกแต่งด้วยตู้ปลา ascii เพื่อให้ฉันมีความสุข :)
Phinet

3
ขอบคุณสำหรับปลาที่ดี (และสีตามวิธีแก้ไขคืออะไร?)
mathreadler

4
คุณสามารถหาปลาได้ที่fishshell.comและไฟล์ config สำหรับ ascii here pastebin.com/yYVYvVeKนอกจากนี้ตัวแก้ไขยังเป็นข้อความประเสริฐ
Phinet

9
x if x else <something>สามารถลดลงเหลือเพียงx or <something>

5

สำหรับฉันคำตอบที่ดีที่สุดคือจาก @ phil-frost ตามด้วย @ wayne-werner's

สิ่งที่ฉันคิดว่าน่าสนใจคือไม่มีใครพูดอะไรเกี่ยวกับความจริงที่ว่าฟังก์ชั่นจะคืนค่าข้อมูลหลายประเภทซึ่งจะทำให้ต้องตรวจสอบชนิดของตัว x เพื่อทำงานต่อไป

ดังนั้นฉันจะผสม @ PhilFrost การตอบสนองกับความคิดในการรักษาประเภทเดียว:

def all_conditions(x):
    yield check_size(x)
    yield check_color(x)
    yield check_tone(x)
    yield check_flavor(x)

def assessed_x(x,func=all_conditions):
    for condition in func(x):
        if condition:
            return x
    return None

ขอให้สังเกตว่าxจะถูกส่งเป็นอาร์กิวเมนต์ แต่ยังall_conditionsถูกนำมาใช้เป็นเครื่องกำเนิดไฟฟ้าผ่านฟังก์ชั่นการตรวจสอบที่ทั้งหมดของพวกเขาได้รับxการตรวจสอบแล้วกลับมาหรือTrue Falseโดยใช้funcกับall_conditionsเป็นค่าเริ่มต้นคุณสามารถใช้assessed_x(x)หรือคุณสามารถส่งผ่านเครื่องกำเนิดไฟฟ้าส่วนบุคคลเพิ่มเติมผ่านfuncหรือคุณสามารถส่งผ่านเครื่องกำเนิดไฟฟ้าส่วนบุคคลเพิ่มเติมผ่านทาง

ด้วยวิธีนี้คุณจะได้รับการxตรวจสอบเพียงครั้งเดียว แต่จะเป็นประเภทเดียวกันเสมอ


4

เป็นการดีที่ฉันจะเขียนcheck_ ฟังก์ชั่นอีกครั้งเพื่อกลับTrueหรือFalseมากกว่าค่า เช็คของคุณจะกลายเป็น

if check_size(x):
    return x
#etc

สมมติว่าคุณxไม่ได้เปลี่ยนรูป, ฟังก์ชั่นของคุณยังสามารถปรับเปลี่ยนได้ (แม้ว่าพวกเขาไม่สามารถเปลี่ยนมัน) - แต่ฟังก์ชั่นที่เรียกว่าcheckไม่ควรจริงๆจะปรับเปลี่ยนมันอยู่แล้ว


3

การเปลี่ยนแปลงเล็กน้อยในตัวอย่างแรกของ Martijns ด้านบนซึ่งจะช่วยหลีกเลี่ยง if ในลูป:

Status = None
for c in [check_size, check_color, check_tone, check_flavor]:
  Status = Status or c();
return Status

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

4
@ Reti43: Status or c()จะข้าม / ลัดวงจรประเมินการโทรc()หากStatusเป็นความจริงดังนั้นรหัสในคำตอบนี้จะไม่ปรากฏขึ้นเพื่อเรียกฟังก์ชั่นเพิ่มเติมใด ๆ นอกเหนือจากรหัสของ OP stackoverflow.com/questions/2580136/…
Neil Slater

2
@NeilSlater True ข้อเสียเดียวที่ฉันเห็นคือกรณีที่ดีที่สุดคือตอนนี้ใน O (n) เพราะผู้ฟังต้องให้เวลา n เมื่อเป็น O (1) ก่อนถ้าฟังก์ชันแรกคืนค่าความจริงใน O (1)
timgeb

1
ใช่คะแนนที่ดี ฉันแค่หวังว่า c () จะใช้เวลาในการประเมินมากกว่าการวนซ้ำลูปที่เกือบจะว่างเปล่า การตรวจสอบรสชาติอาจใช้เวลาช่วงเย็นอย่างน้อยที่สุดถ้าเป็นรสชาติที่ดี
คณิตศาสตร์ที่

3

ฉันชอบ @ timgeb ในระหว่างนี้ฉันต้องการเพิ่มว่าการแสดงNoneในreturnคำสั่งนั้นไม่จำเป็นเนื่องจากการรวบรวมorคำสั่งที่แยกกันจะได้รับการประเมินและไม่มีศูนย์แรก, ไม่มีว่าง, ไม่มีไม่มีส่งคืนและถ้าไม่มีจะNoneถูกส่งกลับ ไม่ว่าจะมีNoneหรือไม่!

ดังนั้นcheck_all_conditions()ฟังก์ชั่นของฉันจะเป็นดังนี้:

def check_all_conditions():
    return check_size() or check_color() or check_tone() or check_flavor()

ใช้timeitกับnumber=10**7ฉันดูเวลาทำงานของจำนวนข้อเสนอแนะ เพื่อประโยชน์ในการเปรียบเทียบฉันใช้random.random()ฟังก์ชันเพื่อส่งคืนสตริงหรือNoneตามตัวเลขสุ่ม นี่คือรหัสทั้งหมด:

import random
import timeit

def check_size():
    if random.random() < 0.25: return "BIG"

def check_color():
    if random.random() < 0.25: return "RED"

def check_tone():
    if random.random() < 0.25: return "SOFT"

def check_flavor():
    if random.random() < 0.25: return "SWEET"

def check_all_conditions_Bernard():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

def check_all_Martijn_Pieters():
    conditions = (check_size, check_color, check_tone, check_flavor)
    for condition in conditions:
        result = condition()
        if result:
            return result

def check_all_conditions_timgeb():
    return check_size() or check_color() or check_tone() or check_flavor() or None

def check_all_conditions_Reza():
    return check_size() or check_color() or check_tone() or check_flavor()

def check_all_conditions_Phinet():
    x = check_size()
    x = x if x else check_color()
    x = x if x else check_tone()
    x = x if x else check_flavor()

    return x if x else None

def all_conditions():
    yield check_size()
    yield check_color()
    yield check_tone()
    yield check_flavor()

def check_all_conditions_Phil_Frost():
    for condition in all_conditions():
        if condition:
            return condition

def main():
    num = 10000000
    random.seed(20)
    print("Bernard:", timeit.timeit('check_all_conditions_Bernard()', 'from __main__ import check_all_conditions_Bernard', number=num))
    random.seed(20)
    print("Martijn Pieters:", timeit.timeit('check_all_Martijn_Pieters()', 'from __main__ import check_all_Martijn_Pieters', number=num))
    random.seed(20)
    print("timgeb:", timeit.timeit('check_all_conditions_timgeb()', 'from __main__ import check_all_conditions_timgeb', number=num))
    random.seed(20)
    print("Reza:", timeit.timeit('check_all_conditions_Reza()', 'from __main__ import check_all_conditions_Reza', number=num))
    random.seed(20)
    print("Phinet:", timeit.timeit('check_all_conditions_Phinet()', 'from __main__ import check_all_conditions_Phinet', number=num))
    random.seed(20)
    print("Phil Frost:", timeit.timeit('check_all_conditions_Phil_Frost()', 'from __main__ import check_all_conditions_Phil_Frost', number=num))

if __name__ == '__main__':
    main()

และนี่คือผลลัพธ์:

Bernard: 7.398444877040768
Martijn Pieters: 8.506569201346597
timgeb: 7.244275416364456
Reza: 6.982133448743038
Phinet: 7.925932800076634
Phil Frost: 11.924794811353031

2

วิธีนี้อยู่นอกกรอบเล็กน้อย แต่ฉันคิดว่าผลลัพธ์สุดท้ายนั้นง่ายอ่านง่ายและดูดี

แนวคิดพื้นฐานคือraiseข้อยกเว้นเมื่อฟังก์ชันใดฟังก์ชันหนึ่งประเมินว่าเป็นความจริงและส่งคืนผลลัพธ์ นี่คือลักษณะ:

def check_conditions():
    try:
        assertFalsey(
            check_size,
            check_color,
            check_tone,
            check_flavor)
    except TruthyException as e:
        return e.trigger
    else:
        return None

คุณจะต้องใช้assertFalseyฟังก์ชันที่ยกข้อยกเว้นเมื่อหนึ่งในอาร์กิวเมนต์ของฟังก์ชันที่เรียกว่าประเมินว่าเป็นความจริง:

def assertFalsey(*funcs):
    for f in funcs:
        o = f()
        if o:
            raise TruthyException(o)

ด้านบนสามารถแก้ไขได้เพื่อให้มีข้อโต้แย้งสำหรับฟังก์ชั่นที่จะได้รับการประเมิน

และแน่นอนคุณจะต้องการTruthyExceptionตัวของมันเอง ข้อยกเว้นนี้ให้สิ่งobjectที่เรียกใช้ข้อยกเว้น:

class TruthyException(Exception):
    def __init__(self, obj, *args):
        super().__init__(*args)
        self.trigger = obj

คุณสามารถเปลี่ยนฟังก์ชั่นดั้งเดิมให้กลายเป็นสิ่งทั่วไปได้มากกว่า:

def get_truthy_condition(*conditions):
    try:
        assertFalsey(*conditions)
    except TruthyException as e:
        return e.trigger
    else:
        return None

result = get_truthy_condition(check_size, check_color, check_tone, check_flavor)

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


//, น่ารัก! ถือว่าเป็น "Pythonic" เพื่อใช้การจัดการข้อยกเว้นสำหรับสิ่งนี้หรือไม่?
นาธาน Basanese

@NathanBasanese Sure- มีการใช้ข้อยกเว้นสำหรับการควบคุมโฟลว์ตลอดเวลา StopIterationเป็นตัวอย่างที่ดี: มีข้อยกเว้นเกิดขึ้นทุกครั้งที่คุณทำซ้ำ สิ่งที่คุณต้องการหลีกเลี่ยงคือการเพิ่มข้อยกเว้นซ้ำ ๆ กันอย่างต่อเนื่องซึ่งจะมีราคาแพง แต่การทำเพียงครั้งเดียวไม่ใช่
Rick สนับสนุนโมนิก้า

// โอ้ฉันจะเอามันที่คุณกำลังหมายถึงสิ่งที่ต้องการprogrammers.stackexchange.com/questions/112463/... ฉันลงคะแนนให้สำหรับคำถามนั้นและสำหรับคำตอบนี้ Python 3 มีเอกสารอยู่ที่นี่: docs.python.org/3/library/stdtypes.html#iterator-typesฉันคิดว่า
Nathan Basanese

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

@ BacklightShining ฉันเห็นด้วย ฉันไม่เคยทำสิ่งนี้ด้วยตัวเอง OP ขอวิธีหลีกเลี่ยงรหัสซ้ำ แต่ฉันคิดว่าสิ่งที่เขาเริ่มต้นนั้นดีมาก
Rick สนับสนุนโมนิก้า

2

วิธีไพทอนอาจใช้การลดขนาด (ตามที่มีคนพูดถึงแล้ว) หรือ itertools (ดังที่แสดงด้านล่าง) แต่สำหรับผมแล้วดูเหมือนว่าเพียงแค่ใช้การลัดวงจรของorผู้ปฏิบัติงานสร้างรหัสที่ชัดเจนขึ้น

from itertools import imap, dropwhile

def check_all_conditions():
    conditions = (check_size,\
        check_color,\
        check_tone,\
        check_flavor)
    results_gen = dropwhile(lambda x:not x, imap(lambda check:check(), conditions))
    try:
        return results_gen.next()
    except StopIteration:
        return None

0

ฉันจะกระโดดเข้าไปที่นี่และไม่เคยเขียน Python สักบรรทัดเดียว แต่ฉันถือว่า if x = check_something(): return xถูกต้องหรือไม่

ถ้าเป็นเช่นนั้น:

def check_all_conditions():

    if (x := check_size()): return x
    if (x := check_color()): return x
    if (x := check_tone()): return x
    if (x := check_flavor()): return x

    return None

1
มันไม่ถูกต้อง Python ไม่ Python ไม่อนุญาตให้คุณใช้โอเปอเรเตอร์การมอบหมายแบบนั้น อย่างไรก็ตามนิพจน์การมอบหมายพิเศษใหม่ถูกเพิ่มเมื่อเร็ว ๆ นี้ดังนั้นตอนนี้คุณสามารถเขียนเอif ( x := check_size() ) :ฟเฟกต์เดียวกันได้
Jack Aidley


-2

ฉันเคยเห็นการใช้งานที่น่าสนใจของงบสวิตช์ / กรณีที่มี dicts ในอดีตที่ทำให้ฉันคำตอบนี้ จากตัวอย่างที่คุณให้มาคุณจะได้รับสิ่งต่อไปนี้ (มันเป็นบ้าusing_complete_sentences_for_function_namesจึงcheck_all_conditionsเปลี่ยนชื่อเป็นstatusดู (1))

def status(k = 'a', s = {'a':'b','b':'c','c':'d','d':None}) :
  select = lambda next, test : test if test else next
  d = {'a': lambda : select(s['a'], check_size()  ),
       'b': lambda : select(s['b'], check_color() ),
       'c': lambda : select(s['c'], check_tone()  ),
       'd': lambda : select(s['d'], check_flavor())}
  while k in d : k = d[k]()
  return k

ฟังก์ชั่นที่เลือกช่วยลดความจำเป็นที่จะต้องโทรหากันcheck_FUNCTIONสองครั้งนั่นคือคุณหลีกเลี่ยงcheck_FUNCTION() if check_FUNCTION() else nextโดยการเพิ่มฟังก์ชั่นเลเยอร์อื่น สิ่งนี้มีประโยชน์สำหรับการใช้งานที่ยาวนาน lambdas ในการดำเนินการ dict delay ของค่าจนกว่าลูป while

เป็นโบนัสที่คุณสามารถแก้ไขคำสั่งการดำเนินการและแม้แต่ข้ามการทดสอบบางอย่างโดยการเปลี่ยนแปลงkและsเช่นk='c',s={'c':'b','b':None}ลดจำนวนการทดสอบและกลับคำสั่งการประมวลผลดั้งเดิม

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

อีกวิธีหนึ่งที่ใช้งานง่ายกว่าอาจเป็นดังนี้:

def status(k=check_size) :
  select = lambda next, test : test if test else next
  d = {check_size  : lambda : select(check_color,  check_size()  ),
       check_color : lambda : select(check_tone,   check_color() ),
       check_tone  : lambda : select(check_flavor, check_tone()  ),
       check_flavor: lambda : select(None,         check_flavor())}
  while k in d : k = d[k]()
  return k
  1. ฉันหมายถึงสิ่งนี้ไม่ได้อยู่ในรูปของ pep8 แต่ในแง่ของการใช้คำอธิบายสั้น ๆ อย่างเดียวแทนประโยค ที่ได้รับ OP อาจปฏิบัติตามอนุสัญญาการเข้ารหัสทำงานหนึ่งฐานรหัสที่มีอยู่บางส่วนหรือไม่สนใจคำศัพท์สั้น ๆ ใน codebase ของพวกเขา

1
บางครั้งผู้คนคลั่งไคล้การตั้งชื่อเมื่อคำเดียวจะทำได้ โดยใช้รหัสของ OP เป็นตัวอย่างก็ไม่น่าที่เขาจะได้ฟังก์ชั่นที่เรียกว่าcheck_no/some/even/prime/every_third/fancy_conditionsแต่เพียงแค่นี้ฟังก์ชันหนึ่งดังนั้นทำไมไม่เรียกมันหรือถ้ามีใครยืนยันstatus check_statusการใช้_all_นั้นไม่จำเป็นเขาไม่รับประกันความสมบูรณ์ของจักรวาล การตั้งชื่อควรใช้ชุดคำหลักที่สอดคล้องกันอย่างแน่นอนซึ่งใช้ประโยชน์จากระยะห่างของชื่อเมื่อทำได้ ประโยคที่ยาวที่สุดจะใช้เป็นเอกสารได้ดีที่สุด หนึ่งคนต้องการความยาวมากกว่า 8-10 ตัวอักษรเพื่ออธิบายสิ่งที่สั้นกระชับ
Carel

1
ฉันเป็นแฟนตัวยงของชื่อฟังก์ชั่นที่ยาวนานเพราะฉันต้องการให้ฟังก์ชั่นระดับสูงกว่าทำเอกสารด้วยตนเอง แต่check_all_conditionsเป็นชื่อที่ไม่ดีเพราะมันไม่ได้ตรวจสอบเงื่อนไขทั้งหมดหากเป็นจริง matches_any_conditionฉันต้องการใช้สิ่งที่ต้องการ
John Hazen

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

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

ฉันกำลังเล็งไปที่ความยืดหยุ่น แก้ไขคำตอบเพื่อรวมตัวแปร 'แฮ็ค' น้อยลง
Carel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.