Câu hỏi Tại sao việc hiểu danh sách ghi vào biến vòng lặp, nhưng máy phát không? [bản sao]


Câu hỏi này đã có câu trả lời ở đây:

Nếu tôi làm điều gì đó với sự hiểu biết danh sách, nó ghi vào một biến cục bộ:

i = 0
test = any([i == 2 for i in xrange(10)])
print i

Điều này in "9". Tuy nhiên, nếu tôi sử dụng một trình tạo, nó không ghi vào một biến cục bộ:

i = 0
test = any(i == 2 for i in xrange(10))
print i

Điều này in "0".

Có lý do chính đáng nào cho sự khác biệt này không? Đây có phải là một quyết định thiết kế, hoặc chỉ là một sản phẩm phụ ngẫu nhiên của cách mà máy phát điện và danh sách hiểu được thực hiện? Cá nhân, nó sẽ có vẻ tốt hơn cho tôi nếu danh sách comprehensions không ghi vào các biến địa phương.


76
2017-11-07 22:32


gốc


Bikeshedding? Tôi sẽ không bỏ phiếu gần đây như là một bản sao, nhưng xem câu trả lời của tôi ở đây - wim
@wim: Cố gắng hiểu một phần không chủ ý của một ngôn ngữ không phải là việc bẻ khóa. - Ry-♦
@ wim: Nếu bằng cách "bikeshedding" bạn có nghĩa là trì hoãn, sau đó bạn là chính xác! Tôi chỉ tò mò, và nó đã gây ra lỗi cho tôi trong quá khứ. - hunse
Xin lỗi, nó chỉ là một cái gì đó đã được hỏi và trả lời nhiều lần rồi. 1 cho bạn anyway, để viết câu hỏi tốt. - wim
Làm thế nào dám bạn so sánh một mối quan tâm ngôn ngữ lập trình quan trọng ngôn ngữ lập trình để lựa chọn màu sơn cho một chiếc xe đạp đổ?! - Kaz


Các câu trả lời:


Người sáng tạo của Python, Guido van Rossum, đề cập đến điều này khi anh ấy viết về biểu thức máy phát được xây dựng thống nhất vào Python 3: (nhấn mạnh mỏ)

Chúng tôi cũng đã thực hiện một thay đổi khác trong Python 3, để cải thiện sự tương đương giữa việc hiểu danh sách và biểu thức trình tạo. Trong Python 2, danh sách hiểu "rò rỉ" biến điều khiển vòng lặp vào phạm vi xung quanh:

x = 'before'
a = [x for x in 1, 2, 3]
print x # this prints '3', not 'before'

Đây là một tạo tác của việc triển khai danh sách ban đầu; nó là một trong những "bí mật nhỏ bẩn" của Python trong nhiều năm. Nó bắt đầu như là một thỏa hiệp có chủ ý để làm cho danh sách hiểu nhanh chóng, và trong khi nó không phải là một lỗ hổng phổ biến cho người mới bắt đầu, nó chắc chắn stung mọi người đôi khi. Đối với biểu thức máy phát điện, chúng tôi không thể làm điều này. Biểu thức máy phát điện được thực hiện bằng cách sử dụng máy phát điện, có thực hiện đòi hỏi một khung thực hiện riêng biệt. Do đó, các biểu thức máy phát điện (đặc biệt nếu chúng lặp qua một chuỗi ngắn) kém hiệu quả hơn so với việc hiểu danh sách.

Tuy nhiên, trong Python 3, chúng tôi quyết định sửa "bí mật nhỏ bẩn" của việc hiểu danh sách bằng cách sử dụng cùng một chiến lược triển khai như đối với các biểu thức của trình tạo. Vì vậy, trong Python 3, ví dụ trên (sau khi sửa đổi để sử dụng print (x) :-) sẽ in 'trước', chứng minh rằng 'x' trong danh sách hiểu tạm thời bóng nhưng không ghi đè lên 'x' ở xung quanh phạm vi.

Vì vậy, trong Python 3 bạn sẽ không thấy điều này xảy ra nữa.

Thật thú vị, đọc dict trong Python 2 cũng không làm điều này; điều này chủ yếu là do sự hiểu biết dict được backported từ Python 3 và như vậy đã có sửa chữa trong đó.

Có một số câu hỏi khác cũng đề cập đến chủ đề này, nhưng tôi chắc rằng bạn đã thấy những câu hỏi đó khi bạn tìm kiếm chủ đề, đúng không? ;)


74
2017-11-07 22:38



:) Tôi đã làm một tìm kiếm ngắn gọn cho điều này, nhưng tôi đã không tìm thấy những bài viết, một phần vì tôi không biết những gì để gọi biến địa phương mà một danh sách hiểu được tạo ra. "Biến vòng lặp" có phải là thuật ngữ ưa thích không? Đó là những gì PEP 289 sử dụng, anyway. Liệu thuật ngữ này cũng áp dụng cho máy phát điện, mặc dù chúng không thực sự có vòng lặp chính thức? - hunse
Các ngữ pháp gọi nó là "mục tiêu", nhưng tôi đoán "biến vòng lặp" vẫn có ý nghĩa nhất. Và tôi cũng nói rằng nó cũng áp dụng cho các máy phát, giống như các chức năng của máy phát đầy đủ - chúng vẫn có một vòng lặp bên trong nhưng chỉ dừng lại cho đến khi yêu cầu lặp tiếp theo. Vì vậy, "biến vòng lặp" cũng hoạt động tốt, tôi sẽ nói :) - poke
@poke Lưu ý rằng target không cần phải là một biến. Ví dụ, nó có thể là một tuple (để giải nén tuple): [x for x,y in something]Tuy nhiên, bạn cũng có thể làm những điều kỳ lạ hơn như: a = [1,2,3]; [1 for a[0] in range(3)], hoặc thậm chí: [1 for something.attribute in iterable]. - Bakuriu
Điều này có nghĩa rằng danh sách hiểu được chậm hơn trong python 3 so với python2? - Jens Timmerman
@JensTimmerman Đoạn sau trực tiếp đề cập đến điều này: “Và trước khi bạn bắt đầu lo lắng về việc hiểu danh sách trở nên chậm chạp trong Python 3: nhờ nỗ lực thực hiện khổng lồ đã đi vào Python 3 để tăng tốc độ nói chung, cả sự hiểu biết danh sách và biểu thức máy phát trong Python 3 thực sự nhanh hơn so với Python 2! ” - poke


Như PEP 289 (Biểu thức Máy phát) giải thích:

Biến vòng lặp (nếu nó là một biến đơn giản hoặc một bộ các biến đơn giản) không được tiếp xúc với hàm xung quanh. Điều này tạo điều kiện cho việc thực hiện và làm cho các trường hợp sử dụng điển hình đáng tin cậy hơn.

Dường như đã được thực hiện vì lý do thực hiện.

Cá nhân, nó sẽ có vẻ tốt hơn cho tôi nếu danh sách comprehensions không ghi vào các biến địa phương.

PEP 289 cũng làm rõ điều này:

Danh sách hiểu cũng "rò rỉ" biến vòng lặp của họ vào phạm vi xung quanh. Điều này cũng sẽ thay đổi trong Python 3.0, do đó định nghĩa ngữ nghĩa của việc hiểu danh sách trong Python 3.0 sẽ tương đương với danh sách ().

Nói cách khác, hành vi mà bạn mô tả thực sự khác với Python 2 nhưng nó đã được sửa trong Python 3.


16
2017-11-07 22:35



không giải thích lý do tại sao listcomps làm phơi bày biến (và PEP 202 không phải là rất hữu ích). tôi cho rằng ban đầu nó phù hợp với ngữ nghĩa của for, và sau này điều này được nhận ra là một ý tưởng tồi. - Eevee
"định nghĩa ngữ nghĩa của việc hiểu danh sách trong Python 3.0 sẽ tương đương với danh sách (<biểu thức máy phát>)" - PEP 289. Điều này dường như là cách hợp lý để thực hiện việc hiểu danh sách, do đó câu hỏi ban đầu của tôi. Tôi đã không nhận ra rằng máy phát điện đến sau. - hunse


Cá nhân, nó sẽ có vẻ tốt hơn cho tôi nếu danh sách comprehensions không ghi vào các biến địa phương.

Bạn nói đúng. Điều này được sửa trong Python 3.x. Các hành vi không thay đổi trong 2.x để nó không ảnh hưởng đến mã hiện có (ab) sử dụng lỗ này.


9
2017-11-07 22:35





Bởi vì bởi vì.

Không, thực sự, đó là nó. Quirk của việc thực hiện. Và được cho là một lỗi, vì nó được sửa trong Python 3.


4
2017-11-07 22:35





Là một sản phẩm phụ của việc đi lang thang như thế nào danh sách-comprehensions thực sự được thực hiện, tôi phát hiện ra một câu trả lời tốt cho câu hỏi của bạn.

Trong Python 2, hãy xem mã byte được tạo ra để hiểu danh sách đơn giản:

>>> s = compile('[i for i in [1, 2, 3]]', '', 'exec')
>>> dis(s)
  1           0 BUILD_LIST               0
              3 LOAD_CONST               0 (1)
              6 LOAD_CONST               1 (2)
              9 LOAD_CONST               2 (3)
             12 BUILD_LIST               3
             15 GET_ITER            
        >>   16 FOR_ITER                12 (to 31)
             19 STORE_NAME               0 (i)
             22 LOAD_NAME                0 (i)
             25 LIST_APPEND              2
             28 JUMP_ABSOLUTE           16
        >>   31 POP_TOP             
             32 LOAD_CONST               3 (None)
             35 RETURN_VALUE  

về cơ bản nó dịch sang đơn giản for-loop, đó là đường cú pháp cho nó. Kết quả là, cùng ngữ nghĩa như đối với for-loops ứng dụng:

a = []
for i in [1, 2, 3]
    a.append(i)
print(i) # 3 leaky

Trong trường hợp danh sách hiểu, (C) Python sử dụng một "tên danh sách ẩn" và một hướng dẫn đặc biệt LIST_APPEND để xử lý sự sáng tạo nhưng thực sự không có gì hơn thế.

Vì vậy, câu hỏi của bạn nên khái quát hóa lý do tại sao Python ghi vào biến vòng lặp for for-loopS; đó là câu trả lời độc đáo bởi một bài đăng blog từ Eli Bendersky.

Python 3, như đã đề cập và bởi những người khác, đã thay đổi ngữ nghĩa danh sách hiểu để phù hợp hơn với các máy phát (bằng cách tạo một đối tượng mã riêng biệt để hiểu) và về cơ bản là cú pháp cho những điều sau đây:

a = [i for i in [1, 2, 3]]

# equivalent to
def __f(it):
    _ = []
    for i in it
        _.append(i)
    return _
a = __f([1, 2, 3])

điều này sẽ không bị rò rỉ vì nó không chạy trong phạm vi trên cùng như Python 2 tương đương. Các i bị rò rỉ, chỉ trong __f và sau đó bị hủy như một biến cục bộ cho hàm đó.

Nếu bạn muốn, hãy xem mã byte được tạo cho Python 3 bởi đang chạy dis('a = [i for i in [1, 2, 3]]'). Bạn sẽ thấy cách một đối tượng mã "ẩn" được tải và sau đó một cuộc gọi hàm được thực hiện ở cuối.


1
2018-02-26 02:26





Một trong những hậu quả tinh tế của bí mật bẩn thỉu được mô tả bằng cách chọc ở trên, là list(...) và [...] không có cùng tác dụng phụ trong Python 2:

In [1]: a = 'Before'
In [2]: list(a for a in range(5))
In [3]: a
Out[3]: 'Before'

Vì vậy, không có tác dụng phụ cho biểu thức máy phát điện bên trong danh sách-constructor, nhưng tác dụng phụ là có trong một danh sách trực tiếp-comprehension:

In [4]: [a for a in range(5)]
In [5]: a
Out[5]: 4

0
2017-10-28 10:51