Python: การแสดงออกของเครื่องกำเนิดเทียบกับอัตราผลตอบแทน


90

ใน Python มีความแตกต่างระหว่างการสร้างวัตถุเครื่องกำเนิดไฟฟ้าผ่านนิพจน์ตัวสร้างกับการใช้คำสั่งผลตอบแทนหรือไม่

ใช้ผลผลิต :

def Generator(x, y):
    for i in xrange(x):
        for j in xrange(y):
            yield(i, j)

การใช้นิพจน์เครื่องกำเนิดไฟฟ้า :

def Generator(x, y):
    return ((i, j) for i in xrange(x) for j in xrange(y))

ฟังก์ชันทั้งสองจะส่งคืนอ็อบเจ็กต์ตัวสร้างซึ่งสร้างทูเปิลเช่น (0,0), (0,1) เป็นต้น

ข้อดีของข้อใดข้อหนึ่ง? ความคิด?


ขอบคุณทุกๆคน! มีข้อมูลดีๆมากมายและข้อมูลอ้างอิงเพิ่มเติมในคำตอบเหล่านี้!


2
เลือกสิ่งที่คุณคิดว่าอ่านได้มากที่สุด
user238424

คำตอบ:


74

มีเพียงความแตกต่างเล็กน้อยในทั้งสอง คุณสามารถใช้disโมดูลเพื่อตรวจสอบสิ่งเหล่านี้ด้วยตัวคุณเอง

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

ดังที่คุณเห็นด้านล่างตัวสร้าง "ผลตอบแทน" (กรณีแรก) มีคำแนะนำเพิ่มเติมสามคำสั่งในการตั้งค่า แต่จากข้อแรกFOR_ITERจะแตกต่างกันเพียงประการเดียว: แนวทาง "ผลตอบแทน" ใช้LOAD_FASTแทนLOAD_DEREFภายในลูป LOAD_DEREFเป็น"ค่อนข้างช้า"กว่าLOAD_FASTจึงทำให้ "ผลผลิต" รุ่นเร็วขึ้นเล็กน้อยกว่าการแสดงออกของเครื่องกำเนิดไฟฟ้าสำหรับค่าพอขนาดใหญ่x(วงด้านนอก) เพราะค่าของyการโหลดเร็วขึ้นเล็กน้อยในแต่ละผ่าน สำหรับค่าที่น้อยกว่าของxนั้นจะช้าลงเล็กน้อยเนื่องจากค่าใช้จ่ายพิเศษของโค้ดการตั้งค่า

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

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

>>> def Generator(x, y):
...     for i in xrange(x):
...         for j in xrange(y):
...             yield(i, j)
...
>>> dis.dis(Generator)
  2           0 SETUP_LOOP              54 (to 57)
              3 LOAD_GLOBAL              0 (xrange)
              6 LOAD_FAST                0 (x)
              9 CALL_FUNCTION            1
             12 GET_ITER
        >>   13 FOR_ITER                40 (to 56)
             16 STORE_FAST               2 (i)

  3          19 SETUP_LOOP              31 (to 53)
             22 LOAD_GLOBAL              0 (xrange)
             25 LOAD_FAST                1 (y)
             28 CALL_FUNCTION            1
             31 GET_ITER
        >>   32 FOR_ITER                17 (to 52)
             35 STORE_FAST               3 (j)

  4          38 LOAD_FAST                2 (i)
             41 LOAD_FAST                3 (j)
             44 BUILD_TUPLE              2
             47 YIELD_VALUE
             48 POP_TOP
             49 JUMP_ABSOLUTE           32
        >>   52 POP_BLOCK
        >>   53 JUMP_ABSOLUTE           13
        >>   56 POP_BLOCK
        >>   57 LOAD_CONST               0 (None)
             60 RETURN_VALUE
>>> def Generator_expr(x, y):
...    return ((i, j) for i in xrange(x) for j in xrange(y))
...
>>> dis.dis(Generator_expr.func_code.co_consts[1])
  2           0 SETUP_LOOP              47 (to 50)
              3 LOAD_FAST                0 (.0)
        >>    6 FOR_ITER                40 (to 49)
              9 STORE_FAST               1 (i)
             12 SETUP_LOOP              31 (to 46)
             15 LOAD_GLOBAL              0 (xrange)
             18 LOAD_DEREF               0 (y)
             21 CALL_FUNCTION            1
             24 GET_ITER
        >>   25 FOR_ITER                17 (to 45)
             28 STORE_FAST               2 (j)
             31 LOAD_FAST                1 (i)
             34 LOAD_FAST                2 (j)
             37 BUILD_TUPLE              2
             40 YIELD_VALUE
             41 POP_TOP
             42 JUMP_ABSOLUTE           25
        >>   45 POP_BLOCK
        >>   46 JUMP_ABSOLUTE            6
        >>   49 POP_BLOCK
        >>   50 LOAD_CONST               0 (None)
             53 RETURN_VALUE

ยอมรับ - สำหรับคำอธิบายโดยละเอียดเกี่ยวกับความแตกต่างโดยใช้ dis. ขอบคุณ!
cschol

ฉันอัปเดตเพื่อรวมลิงก์ไปยังแหล่งที่มาซึ่งอ้างว่าLOAD_DEREF"ค่อนข้างช้ากว่า" ดังนั้นหากประสิทธิภาพมีความสำคัญกับเวลาจริงtimeitก็จะดี การวิเคราะห์ทางทฤษฎีไปไกลแล้ว
Peter Hansen

36

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

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

ป.ล. นี่คือตัวอย่างโครูทีนง่ายๆจากDave Beazley :

def grep(pattern):
    print "Looking for %s" % pattern
    while True:
        line = (yield)
        if pattern in line:
            print line,

# Example use
if __name__ == '__main__':
    g = grep("python")
    g.next()
    g.send("Yeah, but no, but yeah, but no")
    g.send("A series of tubes")
    g.send("python generators rock!")

8
+1 สำหรับการเชื่อมโยงไปยัง David Beazley การนำเสนอของเขาเกี่ยวกับโครูทีนเป็นสิ่งที่น่าสนใจที่สุดที่ฉันเคยอ่านมาเป็นเวลานาน อาจจะไม่มีประโยชน์เท่าการนำเสนอของเขาเกี่ยวกับเครื่องกำเนิดไฟฟ้า แต่ก็น่าทึ่ง
Robert Rossney

18

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

>>> def fibgen():
...    a = b = 1
...    while True:
...        yield a
...        a, b = b, a+b

>>> list(itertools.takewhile((lambda x: x<100), fibgen()))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

5
+1 นั้นยอดเยี่ยมมาก ... ไม่สามารถพูดได้ว่าฉันเคยเห็นการใช้งานที่สั้นและไพเราะแบบนี้โดยไม่ต้องเรียกซ้ำ
JudoWill

ข้อมูลโค้ดง่ายๆหลอกลวง - ฉันคิดว่า Fibonacci จะมีความสุขเมื่อได้เห็นมัน !!
user-asterix

10

ในการใช้งานโปรดสังเกตความแตกต่างระหว่างวัตถุเครื่องกำเนิดไฟฟ้ากับฟังก์ชันเครื่องกำเนิดไฟฟ้า

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

ในทางปฏิบัตินิพจน์ Generator มักจะใช้ "raw" โดยไม่รวมไว้ในฟังก์ชันและส่งคืนอ็อบเจ็กต์เครื่องกำเนิดไฟฟ้า

เช่น:

def range_10_gen_func():
    x = 0
    while x < 10:
        yield x
        x = x + 1

print(list(range_10_gen_func()))
print(list(range_10_gen_func()))
print(list(range_10_gen_func()))

ซึ่งผลลัพธ์:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

เปรียบเทียบกับการใช้งานที่แตกต่างกันเล็กน้อย:

range_10_gen = range_10_gen_func()
print(list(range_10_gen))
print(list(range_10_gen))
print(list(range_10_gen))

ซึ่งผลลัพธ์:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
[]

และเปรียบเทียบกับนิพจน์ตัวสร้าง:

range_10_gen_expr = (x for x in range(10))
print(list(range_10_gen_expr))
print(list(range_10_gen_expr))
print(list(range_10_gen_expr))

ซึ่งยังส่งออก:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
[]

8

การใช้yieldเป็นสิ่งที่ดีถ้านิพจน์ซับซ้อนกว่าการวนซ้ำแบบซ้อนกัน เหนือสิ่งอื่นใดคุณสามารถส่งคืนค่าแรกหรือค่าสุดท้ายพิเศษได้ พิจารณา:

def Generator(x):
  for i in xrange(x):
    yield(i)
  yield(None)

5

เมื่อคิดถึงตัวทำซ้ำitertoolsโมดูล:

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

สำหรับประสิทธิภาพให้พิจารณา itertools.product(*iterables[, repeat])

ผลคูณคาร์ทีเซียนของการวนซ้ำอินพุต

เทียบเท่ากับฟอร์ลูปที่ซ้อนกันในนิพจน์ตัวสร้าง ตัวอย่างเช่นผลตอบแทนเช่นเดียวกับproduct(A, B)((x,y) for x in A for y in B)

>>> import itertools
>>> def gen(x,y):
...     return itertools.product(xrange(x),xrange(y))
... 
>>> [t for t in gen(3,2)]
[(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]
>>> 

4

ใช่มีความแตกต่าง

สำหรับการแสดงออกเครื่องกำเนิดไฟฟ้า(x for var in expr), iter(expr)ถูกเรียกเมื่อการแสดงออกที่มีการสร้างขึ้น

เมื่อใช้defและyieldสร้างเครื่องกำเนิดไฟฟ้าดังใน:

def my_generator():
    for var in expr:
        yield x

g = my_generator()

iter(expr)ยังไม่ได้โทร. จะเรียกเฉพาะเมื่อเปิดซ้ำg(และอาจไม่ถูกเรียกเลย)

ใช้ตัววนซ้ำนี้เป็นตัวอย่าง:

from __future__ import print_function


class CountDown(object):
    def __init__(self, n):
        self.n = n

    def __iter__(self):
        print("ITER")
        return self

    def __next__(self):
        if self.n == 0:
            raise StopIteration()
        self.n -= 1
        return self.n

    next = __next__  # for python2

รหัสนี้:

g1 = (i ** 2 for i in CountDown(3))  # immediately prints "ITER"
print("Go!")
for x in g1:
    print(x)

ในขณะที่:

def my_generator():
    for i in CountDown(3):
        yield i ** 2


g2 = my_generator()
print("Go!")
for x in g2:  # "ITER" is only printed here
    print(x)

เนื่องจากผู้ทำซ้ำส่วนใหญ่ไม่ได้ทำสิ่งต่างๆมากมาย__iter__จึงพลาดพฤติกรรมนี้ได้ง่าย ตัวอย่างในโลกแห่งความเป็นจริงคือ Django QuerySetซึ่งดึงข้อมูลเข้ามา__iter__และdata = (f(x) for x in qs)อาจใช้เวลามากในขณะที่def g(): for x in qs: yield f(x)ตามด้วยdata=g()จะกลับมาทันที

สำหรับข้อมูลเพิ่มเติมและคำนิยามอย่างเป็นทางการอ้างถึงPEP 289 - เครื่องปั่นไฟนิพจน์


0

มีความแตกต่างที่อาจมีความสำคัญในบางบริบทที่ยังไม่ได้รับการชี้ให้เห็น การใช้yieldป้องกันไม่ให้คุณใช้returnเพื่อสิ่งอื่นนอกเหนือจากการเพิ่ม StopIteration โดยปริยาย (และแก้ไขสิ่งที่เกี่ยวข้อง)สิ่งที่เกี่ยวข้อง)

ซึ่งหมายความว่ารหัสนี้มีรูปแบบไม่ถูกต้อง (และการป้อนให้กับล่ามจะให้ข้อมูลAttributeError)

class Tea:

    """With a cloud of milk, please"""

    def __init__(self, temperature):
        self.temperature = temperature

def mary_poppins_purse(tea_time=False):
    """I would like to make one thing clear: I never explain anything."""
    if tea_time:
        return Tea(355)
    else:
        for item in ['lamp', 'mirror', 'coat rack', 'tape measure', 'ficus']:
            yield item

print(mary_poppins_purse(True).temperature)

ในทางกลับกันรหัสนี้ใช้งานได้เหมือนมีเสน่ห์:

class Tea:

    """With a cloud of milk, please"""

    def __init__(self, temperature):
        self.temperature = temperature

def mary_poppins_purse(tea_time=False):
    """I would like to make one thing clear: I never explain anything."""
    if tea_time:
        return Tea(355)
    else:
        return (item for item in ['lamp', 'mirror', 'coat rack',
                                  'tape measure', 'ficus'])

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