Trả về hoặc sản lượng từ một chức năng gọi một máy phát điện?


30

Tôi có một máy phát điện generatorvà cũng là một phương pháp tiện lợi cho nó - generate_all.

def generator(some_list):
  for i in some_list:
    yield do_something(i)

def generate_all():
  some_list = get_the_list()
  return generator(some_list) # <-- Is this supposed to be return or yield?

Nên generate_all returnhay yield? Tôi muốn người dùng của cả hai phương thức sử dụng nó giống nhau, tức là

for x in generate_all()

nên bằng

some_list = get_the_list()
for x in generate(some_list)

2
Có một lý do để sử dụng một trong hai. Trong ví dụ này, sự trở lại hiệu quả hơn
Mad Physicist

1
Điều này gợi cho tôi về một câu hỏi tương tự mà tôi đã từng đặt ra: năng suất từ ​​iterable và vs return return iter (iterable) . Mặc dù không đặc biệt về máy phát điện, về cơ bản nó giống như máy phát điện và trình vòng lặp khá giống nhau trong python. Ngoài ra, chiến lược so sánh mã byte theo đề xuất của câu trả lời có thể được sử dụng ở đây.
PeterE

Câu trả lời:


12

Các trình tạo được đánh giá lười biếng vì vậy returnhoặc yieldsẽ hành xử khác đi khi bạn gỡ lỗi mã của mình hoặc nếu một ngoại lệ được đưa ra.

Với returnbất kỳ ngoại lệ nào xảy ra trong bạn generatorsẽ không biết gì generate_all, đó là vì khi generatorthực sự được thực thi, bạn đã rời khỏi generate_allchức năng. Với yieldtrong đó nó sẽ có generate_alltrong truy nguyên.

def generator(some_list):
    for i in some_list:
        raise Exception('exception happened :-)')
        yield i

def generate_all():
    some_list = [1,2,3]
    return generator(some_list)

for item in generate_all():
    ...
Exception                                 Traceback (most recent call last)
<ipython-input-3-b19085eab3e1> in <module>
      8     return generator(some_list)
      9 
---> 10 for item in generate_all():
     11     ...

<ipython-input-3-b19085eab3e1> in generator(some_list)
      1 def generator(some_list):
      2     for i in some_list:
----> 3         raise Exception('exception happened :-)')
      4         yield i
      5 

Exception: exception happened :-)

Và nếu nó đang sử dụng yield from:

def generate_all():
    some_list = [1,2,3]
    yield from generator(some_list)

for item in generate_all():
    ...
Exception                                 Traceback (most recent call last)
<ipython-input-4-be322887df35> in <module>
      8     yield from generator(some_list)
      9 
---> 10 for item in generate_all():
     11     ...

<ipython-input-4-be322887df35> in generate_all()
      6 def generate_all():
      7     some_list = [1,2,3]
----> 8     yield from generator(some_list)
      9 
     10 for item in generate_all():

<ipython-input-4-be322887df35> in generator(some_list)
      1 def generator(some_list):
      2     for i in some_list:
----> 3         raise Exception('exception happened :-)')
      4         yield i
      5 

Exception: exception happened :-)

Tuy nhiên điều này đi kèm với chi phí hiệu suất. Lớp máy phát bổ sung không có một số chi phí. Vì vậy, returnnói chung sẽ nhanh hơn một chút so với yield from ...(hoặc for item in ...: yield item). Trong hầu hết các trường hợp, điều này sẽ không quan trọng lắm, bởi vì bất cứ điều gì bạn làm trong trình tạo thường chiếm ưu thế trong thời gian chạy để lớp bổ sung sẽ không được chú ý.

Tuy nhiên yieldcó một số lợi thế bổ sung: Bạn không bị hạn chế trong một lần lặp duy nhất, bạn cũng có thể dễ dàng mang lại các mục bổ sung:

def generator(some_list):
    for i in some_list:
        yield i

def generate_all():
    some_list = [1,2,3]
    yield 'start'
    yield from generator(some_list)
    yield 'end'

for item in generate_all():
    print(item)
start
1
2
3
end

Trong trường hợp của bạn, các thao tác khá đơn giản và tôi không biết liệu có cần thiết phải tạo nhiều hàm cho việc này hay không, người ta có thể dễ dàng sử dụng biểu thức dựng sẵn maphoặc biểu thức tạo:

map(do_something, get_the_list())          # map
(do_something(i) for i in get_the_list())  # generator expression

Cả hai nên giống hệt nhau (ngoại trừ một số khác biệt khi ngoại lệ xảy ra) để sử dụng. Và nếu họ cần một cái tên mô tả hơn, thì bạn vẫn có thể bọc chúng trong một chức năng.

Có nhiều trình trợ giúp bao bọc các hoạt động rất phổ biến trên các trình lặp được tích hợp sẵn và các trình trợ giúp khác có thể được tìm thấy trong itertoolsmô-đun tích hợp. Trong những trường hợp đơn giản như vậy, tôi chỉ đơn giản là dùng đến những thứ này và chỉ cho những trường hợp không tầm thường hãy viết máy phát điện của riêng bạn.

Nhưng tôi cho rằng mã thực của bạn phức tạp hơn nên có thể không áp dụng được nhưng tôi nghĩ nó sẽ không phải là một câu trả lời hoàn chỉnh mà không đề cập đến các lựa chọn thay thế.


17

Có lẽ bạn đang tìm kiếm Phái đoàn máy phát điện (PEP380)

Đối với các trình vòng lặp đơn giản, yield from iterablevề cơ bản chỉ là một dạng rút gọn củafor item in iterable: yield item

def generator(iterable):
  for i in iterable:
    yield do_something(i)

def generate_all():
  yield from generator(get_the_list())

Nó khá súc tích và cũng có một số lợi thế khác, chẳng hạn như có thể xâu chuỗi các vòng lặp tùy ý / khác nhau!


Oh bạn có nghĩa là đặt tên của list? Đó là một ví dụ tồi, không phải mã thực được dán trong câu hỏi, tôi có lẽ nên chỉnh sửa nó.
hyankov

Vâng - không bao giờ sợ hãi, tôi khá có lỗi với mã ví dụ thậm chí sẽ không chạy ngay từ lần hỏi đầu tiên ..
ti7

2
Đầu tiên có thể là một lót quá :). yield from map(do_something, iterable)hoặc thậm chíyield from (do_something(x) for x in iterable)
Nhà vật lý điên

2
"Đó là mã ví dụ cho đến hết!"
ti7

3
Bạn chỉ cần ủy quyền nếu bạn, chính bạn, làm một việc gì đó ngoài việc trả lại máy phát điện mới. Nếu bạn chỉ trả lại máy phát điện mới, không cần ủy quyền. Vì vậy, yield fromlà vô nghĩa trừ khi trình bao bọc của bạn làm một cái gì đó khác-y.
ShadowRanger

14

return generator(list)làm những gì bạn muốn. Nhưng lưu ý rằng

yield from generator(list)

sẽ tương đương, nhưng với cơ hội mang lại nhiều giá trị hơn sau khi generatorcạn kiệt. Ví dụ:

def generator_all_and_then_some():
    list = get_the_list()
    yield from generator(list)
    yield "one last thing"

5
Tôi tin rằng có một sự khác biệt tinh tế giữa yield fromreturnkhi người tiêu dùng máy phát điện throwsngoại lệ bên trong nó - và với các hoạt động khác bị ảnh hưởng bởi dấu vết ngăn xếp.
WorldSEnder

9

Hai câu lệnh sau sẽ xuất hiện tương đương về chức năng trong trường hợp cụ thể này:

return generator(list)

yield from generator(list)

Cái sau gần giống như

for i in generator(list):
    yield i

Câu returnlệnh trả về trình tạo mà bạn đang tìm kiếm. Một câu lệnh yield fromhoặc yieldbiến toàn bộ chức năng của bạn thành một cái gì đó trả về một trình tạo, đi qua cái mà bạn đang tìm kiếm.

Từ quan điểm người dùng, không có sự khác biệt. Tuy nhiên, bên trong, returnđược cho là hiệu quả hơn vì nó không bao bọc generator(list)trong một máy phát qua đường siêu tốc. Nếu bạn có kế hoạch thực hiện bất kỳ xử lý nào trên các phần tử của trình tạo gói, hãy sử dụng một số hình thức yieldtất nhiên.


4

Bạn sẽ làm returnđiều đó.

yielding * sẽ gây ra generate_all()việc đánh giá chính trình tạo và gọi nextvào trình tạo bên ngoài đó sẽ trả về trình tạo bên trong được trả về bởi hàm đầu tiên, đây không phải là điều bạn muốn.

* Không bao gồm yield from

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.