Lưu ý : đây là một lỗi trong việc CPython xử lý các yield
biểu thức hiểu và trình tạo, được sửa trong Python 3.8, với cảnh báo không dùng nữa trong Python 3.7. Xem báo cáo lỗi Python và các mục nhập Có gì mới cho Python 3.7 và Python 3.8 .
Biểu thức trình tạo, và tập hợp và hiểu chính tả được biên dịch thành các đối tượng hàm (trình tạo). Trong Python 3, các phần hiểu danh sách có cùng cách xử lý; về bản chất, tất cả chúng đều là một phạm vi lồng nhau mới.
Bạn có thể thấy điều này nếu bạn cố gắng tháo rời một biểu thức trình tạo:
>>> dis.dis(compile("(i for i in range(3))", '', 'exec'))
1 0 LOAD_CONST 0 (<code object <genexpr> at 0x10f7530c0, file "", line 1>)
3 LOAD_CONST 1 ('<genexpr>')
6 MAKE_FUNCTION 0
9 LOAD_NAME 0 (range)
12 LOAD_CONST 2 (3)
15 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
18 GET_ITER
19 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
22 POP_TOP
23 LOAD_CONST 3 (None)
26 RETURN_VALUE
>>> dis.dis(compile("(i for i in range(3))", '', 'exec').co_consts[0])
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 11 (to 17)
6 STORE_FAST 1 (i)
9 LOAD_FAST 1 (i)
12 YIELD_VALUE
13 POP_TOP
14 JUMP_ABSOLUTE 3
>> 17 LOAD_CONST 0 (None)
20 RETURN_VALUE
Ở trên cho thấy rằng một biểu thức trình tạo được biên dịch thành một đối tượng mã, được tải dưới dạng một hàm ( MAKE_FUNCTION
tạo đối tượng hàm từ đối tượng mã). Các .co_consts[0]
tài liệu tham khảo cho chúng ta thấy đối tượng mã được tạo cho các biểu hiện, và nó sử dụng YIELD_VALUE
giống như một chức năng máy phát điện sẽ.
Như vậy, yield
biểu thức hoạt động trong ngữ cảnh đó, vì trình biên dịch coi đây là những hàm trong ngụy trang.
Đây là một lỗi; yield
không có vị trí trong các biểu thức này. Ngữ pháp Python trước Python 3.7 cho phép nó (đó là lý do tại sao mã có thể biên dịch được), nhưng yield
đặc tả biểu thức cho thấy rằng việc sử dụng yield
ở đây không thực sự hoạt động:
Biểu thức sản lượng chỉ được sử dụng khi xác định hàm bộ tạo và do đó chỉ có thể được sử dụng trong phần thân của định nghĩa hàm.
Đây đã được xác nhận là một lỗi trong số 10544 . Giải pháp của lỗi là sử dụng yield
và yield from
sẽ tăng a SyntaxError
trong Python 3.8 ; trong Python 3.7, nó tăng mộtDeprecationWarning
để đảm bảo mã ngừng sử dụng cấu trúc này. Bạn sẽ thấy cảnh báo tương tự trong Python 2.7.15 trở lên nếu bạn sử dụng công -3
tắc dòng lệnh bật cảnh báo tương thích Python 3.
Cảnh báo 3.7.0b1 trông như thế này; chuyển cảnh báo thành lỗi sẽ cho bạn một SyntaxError
ngoại lệ, giống như bạn làm trong 3.8:
>>> [(yield i) for i in range(3)]
<stdin>:1: DeprecationWarning: 'yield' inside list comprehension
<generator object <listcomp> at 0x1092ec7c8>
>>> import warnings
>>> warnings.simplefilter('error')
>>> [(yield i) for i in range(3)]
File "<stdin>", line 1
SyntaxError: 'yield' inside list comprehension
Sự khác biệt giữa cách thức hoạt động trong cách yield
hiểu danh sách và yield
trong biểu thức trình tạo bắt nguồn từ sự khác biệt trong cách hai biểu thức này được triển khai. Trong Python 3, tính năng hiểu danh sách sử dụng LIST_APPEND
các lệnh gọi để thêm phần trên cùng của ngăn xếp vào danh sách đang được xây dựng, trong khi biểu thức trình tạo thay thế mang lại giá trị đó. Thêm vào (yield <expr>)
chỉ thêm một YIELD_VALUE
opcode khác vào:
>>> dis.dis(compile("[(yield i) for i in range(3)]", '', 'exec').co_consts[0])
1 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 13 (to 22)
9 STORE_FAST 1 (i)
12 LOAD_FAST 1 (i)
15 YIELD_VALUE
16 LIST_APPEND 2
19 JUMP_ABSOLUTE 6
>> 22 RETURN_VALUE
>>> dis.dis(compile("((yield i) for i in range(3))", '', 'exec').co_consts[0])
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 12 (to 18)
6 STORE_FAST 1 (i)
9 LOAD_FAST 1 (i)
12 YIELD_VALUE
13 YIELD_VALUE
14 POP_TOP
15 JUMP_ABSOLUTE 3
>> 18 LOAD_CONST 0 (None)
21 RETURN_VALUE
Mã YIELD_VALUE
opcode ở các chỉ số bytecode lần lượt là 15 và 12 là phụ, một con chim cu gáy trong tổ. Vì vậy, đối với trình tạo danh sách-hiểu-biến, bạn có 1 lợi suất tạo ra phần trên cùng của ngăn xếp mỗi lần (thay thế đầu của ngăn xếp bằng yield
giá trị trả về) và đối với biến thể biểu thức trình tạo, bạn mang lại ở trên cùng của ngăn xếp ( số nguyên) và sau đó lại cho kết quả , nhưng bây giờ ngăn xếp chứa giá trị trả về của yield
và bạn nhận được None
lần thứ hai đó.
Sau đó, list
đối với khả năng hiểu danh sách, đầu ra đối tượng dự định vẫn được trả về, nhưng Python 3 coi đây là trình tạo nên giá trị trả về thay vào đó được gắn với StopIteration
ngoại lệ dưới dạng value
thuộc tính:
>>> from itertools import islice
>>> listgen = [(yield i) for i in range(3)]
>>> list(islice(listgen, 3))
[0, 1, 2]
>>> try:
... next(listgen)
... except StopIteration as si:
... print(si.value)
...
[None, None, None]
Các None
đối tượng đó là giá trị trả về từ các yield
biểu thức.
Và để nhắc lại điều này một lần nữa; vấn đề tương tự cũng áp dụng cho từ điển và thiết lập khả năng hiểu trong Python 2 và Python 3; trong Python 2, các yield
giá trị trả về vẫn được thêm vào từ điển hoặc đối tượng tập hợp dự định và giá trị trả về được 'mang lại' sau cùng thay vì được đính kèm với StopIteration
ngoại lệ:
>>> list({(yield k): (yield v) for k, v in {'foo': 'bar', 'spam': 'eggs'}.items()})
['bar', 'foo', 'eggs', 'spam', {None: None}]
>>> list({(yield i) for i in range(3)})
[0, 1, 2, set([None])]
yield-atom
được phép bên trong một biểu thức (bên trong một hàm trình tạo). Điều này có thể còn nhiều vấn đề hơn nếuyield-atom
bằng cách nào đó nó được thực hiện sai.