2010-11-16 27 views
95

Việc hiểu danh sách đang có một số tương tác không mong muốn với phạm vi. Đây có phải là hành vi mong đợi không?Tên rebind danh sách đọc Python ngay cả sau phạm vi hiểu. Thê nay đung không?

Tôi đã có một phương pháp:

def leave_room(self, uid): 
    u = self.user_by_id(uid) 
    r = self.rooms[u.rid] 

    other_uids = [ouid for ouid in r.users_by_id.keys() if ouid != u.uid] 
    other_us = [self.user_by_id(uid) for uid in other_uids] 

    r.remove_user(uid) # OOPS! uid has been re-bound by the list comprehension above 

    # Interestingly, it's rebound to the last uid in the list, so the error only shows 
    # up when len > 1 

Lúc nguy cơ rên rỉ, đây là một nguồn tàn bạo của lỗi. Khi tôi viết mã mới, tôi chỉ thỉnh thoảng tìm thấy những lỗi rất lạ do rebinding - ngay cả bây giờ tôi biết đó là một vấn đề. Tôi cần phải thực hiện một quy tắc như "luôn luôn bắt đầu các cuộc chiến temp tạm thời trong danh sách sự hiểu biết với dấu gạch dưới", nhưng thậm chí đó không phải là chứng minh ngu ngốc.

Thực tế là có loại thời gian chờ bom ngẫu nhiên này phủ nhận tất cả "dễ sử dụng" đẹp của tính năng hiểu danh sách.

+5

-1: "nguồn tàn bạo của lỗi"? Khó khăn. Tại sao lại chọn một thuật ngữ tranh luận như vậy? Nói chung các lỗi đắt nhất là những hiểu lầm về yêu cầu và các lỗi logic đơn giản. Loại lỗi này là một vấn đề tiêu chuẩn trong rất nhiều ngôn ngữ lập trình. Tại sao gọi nó là 'tàn bạo'? –

+33

Nó vi phạm nguyên tắc ít ngạc nhiên nhất. Nó cũng không được đề cập trong tài liệu python về việc hiểu danh sách, tuy nhiên nó đề cập đến nhiều lần chúng dễ dàng và tiện lợi như thế nào. Về cơ bản nó là một mảnh đất tồn tại bên ngoài mô hình ngôn ngữ của tôi, và do đó là không thể cho tôi thấy trước. –

+26

+1 cho "nguồn lỗi nghiêm trọng". Từ 'tàn bạo' là * hoàn toàn * hợp lý. – Nathaniel

Trả lời

131

Danh sách comprehensions rò rỉ biến kiểm soát vòng lặp bằng Python 2 nhưng không bằng Python 3. Dưới đây là Guido van Rossum (tác giả của Python) explaining lịch sử đằng sau này:

Chúng tôi cũng thực hiện một sự thay đổi trong Python 3 , để cải thiện sự tương đương giữa danh sách số hiểu và phát biểu . Trong Python 2, danh sách hiểu "rò rỉ" sự kiểm soát vòng lặp biến thành 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 artifact của bản gốc thi hành comprehensions danh sách; nó là một trong những "bí mật nhỏ bí mật" của Python trong nhiều năm.Nó bắt đầu với tư cách là một sự thỏa hiệp có chủ ý để làm cho danh sách sự hiểu biết nhanh chóng và trong khi đó 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 khiến mọi người chỉ thỉnh thoảng . Đối với máy phát điện biểu thức chúng tôi không thể làm điều này. Biểu thức máy phát điện được triển khai bằng cách sử dụng máy phát điện có thực thi yêu cầu một khung thực thi riêng biệt. Vì vậy, 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 chữa các "bí mật nhỏ bẩn" của danh sách comprehensions bằng cách sử dụng chiến lược thực hiện tương tự như đối biểu thức máy phát điện. Do đó, trong Python 3, ví dụ trên (sau sửa đổi để sử dụng in (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 đè ' x ' trong phạm vi xung quanh.

+7

Tôi sẽ thêm rằng mặc dù Guido gọi nó là một "bí mật bẩn nhỏ", nhiều người coi nó là một tính năng, không phải là một lỗi. –

+30

Cũng lưu ý rằng bây giờ trong 2,7, thiết lập và từ điển comprehensions (và máy phát điện) có phạm vi tư nhân, nhưng danh sách comprehensions vẫn không. Trong khi điều này làm cho một số ý nghĩa trong đó trước đây đã được tất cả các back-ported từ Python 3, nó thực sự làm cho sự tương phản với jarring danh sách hiểu. –

+4

Tôi biết đây là một câu hỏi điên rồ cũ, nhưng _why_ đã làm một số xem xét nó một tính năng của ngôn ngữ? Có bất cứ điều gì có lợi cho loại rò rỉ biến này không? –

7

Có, chuyển nhượng xảy ra ở đó, giống như trong vòng for. Không có phạm vi mới nào được tạo.

Đây chắc chắn là hành vi mong đợi: trên mỗi chu kỳ, giá trị được gắn với tên bạn chỉ định. Ví dụ:

>>> x=0 
>>> a=[1,54,4,2,32,234,5234,] 
>>> [x for x in a if x>32] 
[54, 234, 5234] 
>>> x 
5234 

Khi đã nhận ra, có vẻ như đủ dễ dàng để tránh: không sử dụng tên hiện có cho các biến trong phạm vi hiểu.

45

Có, danh sách hiểu "rò rỉ" biến của chúng trong Python 2.x, giống như đối với vòng lặp.

Nhìn lại, điều này đã được công nhận là một sai lầm, và nó đã tránh được với các biểu thức máy phát điện. EDIT: Như Matt B. notes nó cũng được tránh khi thiết lập và hiểu từ điển cú pháp được backported từ Python hành vi 3.

Danh sách comprehensions' đã phải bị bỏ lại vì nó là bằng Python 2, nhưng nó cố định hoàn toàn bằng Python 3.

Điều này có nghĩa rằng trong tất cả:

list(x for x in a if x>32) 
set(x//4 for x in a if x>32)   # just another generator exp. 
dict((x, x//16) for x in a if x>32) # yet another generator exp. 
{x//4 for x in a if x>32}   # 2.7+ syntax 
{x: x//16 for x in a if x>32}  # 2.7+ syntax 

các x luôn là địa phương để các biểu hiện trong khi những:

[x for x in a if x>32] 
set([x//4 for x in a if x>32])   # just another list comp. 
dict([(x, x//16) for x in a if x>32]) # yet another list comp. 

trong Python 2.x tất cả bị rò rỉ biến số x đến phạm vi xung quanh.

2

Điều thú vị là điều này không ảnh hưởng đến từ điển hoặc cài đặt hiểu.

>>> [x for x in range(1, 10)] 
[1, 2, 3, 4, 5, 6, 7, 8, 9] 
>>> x 
9 
>>> {x for x in range(1, 5)} 
set([1, 2, 3, 4]) 
>>> x 
9 
>>> {x:x for x in range(1, 100)} 
{1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12, 13: 13, 14: 14, 15: 15, 16: 16, 17: 17, 18: 18, 19: 19, 20: 20, 21: 21, 22: 22, 23: 23, 24: 24, 25: 25, 26: 26, 27: 27, 28: 28, 29: 29, 30: 30, 31: 31, 32: 32, 33: 33, 34: 34, 35: 35, 36: 36, 37: 37, 38: 38, 39: 39, 40: 40, 41: 41, 42: 42, 43: 43, 44: 44, 45: 45, 46: 46, 47: 47, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 58: 58, 59: 59, 60: 60, 61: 61, 62: 62, 63: 63, 64: 64, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 91: 91, 92: 92, 93: 93, 94: 94, 95: 95, 96: 96, 97: 97, 98: 98, 99: 99} 
>>> x 
9 

Tuy nhiên, nó đã được sửa trong 3 như đã lưu ý ở trên.

+0

Cú pháp đó không hoạt động chút nào trong Python 2.6. Bạn đang nói về Python 2.7? –

+0

Python 2.6 có danh sách chỉ hiểu cũng như Python 3.0. 3,1 thêm bộ và từ điển comprehensions và những đã được chuyển đến 2,7. Xin lỗi nếu điều đó không rõ ràng. Nó có nghĩa là để lưu ý một giới hạn cho câu trả lời khác, và các phiên bản nó áp dụng cho là không hoàn toàn đơn giản. –

+0

Trong khi tôi có thể tưởng tượng làm cho một đối số rằng có những trường hợp sử dụng python 2,7 cho mã mới có ý nghĩa, tôi không thể nói tương tự cho python 2.6 ... Ngay cả khi 2.6 là gì đi kèm với hệ điều hành của bạn, bạn không bị mắc kẹt với nó. Cân nhắc cài đặt virtualenv và sử dụng 3.6 cho mã mới! –

0

một số cách giải quyết, cho python 2.6, khi hành vi này là không mong muốn

# python 
Python 2.6.6 (r266:84292, Aug 9 2016, 06:11:56) 
Type "help", "copyright", "credits" or "license" for more information. 
>>> x=0 
>>> a=list(x for x in xrange(9)) 
>>> x 
0 
>>> a=[x for x in xrange(9)] 
>>> x 
8 
Các vấn đề liên quan