Phạm vi lớp và danh sách, tập hợp hoặc hiểu từ điển, cũng như các biểu thức trình tạo không trộn lẫn.
Tại sao; hoặc, từ chính thức về điều này
Trong Python 3, việc hiểu danh sách được cung cấp một phạm vi thích hợp (không gian tên cục bộ) của riêng chúng, để ngăn chặn các biến cục bộ của chúng chảy vào phạm vi xung quanh (xem tên rebind hiểu danh sách Python ngay cả sau phạm vi hiểu. Có đúng không? ). Thật tuyệt vời khi sử dụng một sự hiểu biết danh sách như vậy trong một mô-đun hoặc trong một chức năng, nhưng trong các lớp, phạm vi là một chút, uhm, lạ .
Điều này được ghi lại trong pep 227 :
Tên trong phạm vi lớp học không thể truy cập. Tên được giải quyết trong phạm vi chức năng kèm theo trong cùng. Nếu một định nghĩa lớp xảy ra trong một chuỗi các phạm vi lồng nhau, quá trình phân giải sẽ bỏ qua các định nghĩa lớp.
và trong class
tài liệu tuyên bố ghép :
Bộ phần mềm của lớp sau đó được thực thi trong khung thực thi mới (xem phần Đặt tên và ràng buộc ), sử dụng không gian tên cục bộ mới được tạo và không gian tên toàn cục ban đầu. (Thông thường, bộ chỉ chứa các định nghĩa hàm.) Khi bộ của lớp kết thúc thực thi, khung thực thi của nó bị loại bỏ nhưng không gian tên cục bộ của nó được lưu . [4] Sau đó, một đối tượng lớp được tạo bằng danh sách kế thừa cho các lớp cơ sở và không gian tên cục bộ đã lưu cho từ điển thuộc tính.
Nhấn mạnh mỏ; khung thực hiện là phạm vi tạm thời.
Bởi vì phạm vi được định nghĩa lại như các thuộc tính trên một đối tượng lớp, cho phép nó được sử dụng như một phạm vi không nhắm mục tiêu cũng dẫn đến hành vi không xác định; Điều gì sẽ xảy ra nếu một phương thức lớp được gọi x
là biến phạm vi lồng nhau Foo.x
, chẳng hạn, cũng thao tác như vậy? Quan trọng hơn, điều đó có nghĩa gì đối với các lớp con Foo
? Python có để điều trị một phạm vi lớp học khác nhau vì nó rất khác so với một phạm vi chức năng.
Cuối cùng, nhưng chắc chắn không kém phần quan trọng, phần Đặt tên và ràng buộc được liên kết trong tài liệu mô hình Thực thi đề cập rõ ràng phạm vi lớp:
Phạm vi của các tên được định nghĩa trong một khối lớp được giới hạn trong khối lớp; nó không mở rộng đến các khối mã của các phương thức - điều này bao gồm hiểu và biểu thức trình tạo vì chúng được thực hiện bằng cách sử dụng phạm vi hàm. Điều này có nghĩa là những điều sau đây sẽ thất bại:
class A:
a = 42
b = list(a + i for i in range(10))
Vì vậy, để tóm tắt: bạn không thể truy cập phạm vi lớp từ các hàm, liệt kê mức độ hiểu hoặc biểu thức trình tạo trong phạm vi đó; họ hành động như thể phạm vi đó không tồn tại. Trong Python 2, việc hiểu danh sách được triển khai bằng phím tắt, nhưng trong Python 3, chúng có phạm vi chức năng riêng (vì lẽ ra chúng phải có tất cả cùng) và do đó, ví dụ của bạn bị phá vỡ. Các kiểu hiểu khác có phạm vi riêng của chúng bất kể phiên bản Python, vì vậy một ví dụ tương tự với cách hiểu tập hợp hoặc chính tả sẽ phá vỡ trong Python 2.
# Same error, in Python 2 or 3
y = {x: x for i in range(1)}
Ngoại lệ (nhỏ); hoặc, tại sao một phần vẫn có thể hoạt động
Có một phần của biểu thức hiểu hoặc trình tạo thực thi trong phạm vi xung quanh, bất kể phiên bản Python. Đó sẽ là biểu thức cho lần lặp ngoài cùng. Trong ví dụ của bạn, đó là range(1)
:
y = [x for i in range(1)]
# ^^^^^^^^
Do đó, sử dụng x
trong biểu thức đó sẽ không gây ra lỗi:
# Runs fine
y = [i for i in range(x)]
Điều này chỉ áp dụng cho lần lặp ngoài cùng; nếu một sự hiểu biết có nhiều for
mệnh đề, các lần lặp cho for
các mệnh đề bên trong được đánh giá trong phạm vi của sự hiểu biết:
# NameError
y = [i for i in range(1) for j in range(x)]
Quyết định thiết kế này được đưa ra để đưa ra một lỗi trong thời gian tạo genEx thay vì thời gian lặp khi tạo ra lần lặp ngoài cùng của biểu thức máy phát ném lỗi hoặc khi lần lặp ngoài cùng hóa ra không thể lặp lại được. Hiểu được chia sẻ hành vi này cho sự nhất quán.
Nhìn dưới mui xe; hoặc, chi tiết hơn bạn muốn
Bạn có thể thấy tất cả điều này trong hành động bằng cách sử dụng dis
mô-đun . Tôi đang sử dụng Python 3.3 trong các ví dụ sau, bởi vì nó thêm các tên đủ điều kiện xác định gọn gàng các đối tượng mã mà chúng tôi muốn kiểm tra. Mã byte được tạo ra có chức năng giống hệt với Python 3.2.
Để tạo một lớp, Python về cơ bản lấy toàn bộ bộ phần tạo nên phần thân lớp (vì vậy mọi thứ thụt sâu hơn một cấp so với class <name>:
dòng) và thực hiện như thể đó là một hàm:
>>> import dis
>>> def foo():
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo)
2 0 LOAD_BUILD_CLASS
1 LOAD_CONST 1 (<code object Foo at 0x10a436030, file "<stdin>", line 2>)
4 LOAD_CONST 2 ('Foo')
7 MAKE_FUNCTION 0
10 LOAD_CONST 2 ('Foo')
13 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
16 STORE_FAST 0 (Foo)
5 19 LOAD_FAST 0 (Foo)
22 RETURN_VALUE
Đầu tiên LOAD_CONST
ở đó tải một đối tượng mã cho Foo
thân lớp, sau đó biến nó thành một hàm và gọi nó. Các kết quả của cuộc gọi mà sau đó được sử dụng để tạo ra các không gian tên của lớp, của nó __dict__
. Càng xa càng tốt.
Điều cần lưu ý ở đây là mã byte chứa một đối tượng mã lồng nhau; trong Python, tất cả các định nghĩa lớp, hàm, hiểu và trình tạo đều được biểu diễn dưới dạng các đối tượng mã không chỉ chứa mã byte, mà còn các cấu trúc đại diện cho các biến cục bộ, hằng, biến được lấy từ toàn cục và các biến được lấy từ phạm vi lồng nhau. Mã byte được biên dịch đề cập đến các cấu trúc đó và trình thông dịch python biết cách truy cập vào các mã được cung cấp bởi các mã byte được trình bày.
Điều quan trọng cần nhớ ở đây là Python tạo các cấu trúc này tại thời gian biên dịch; các class
bộ là một đối tượng mã ( <code object Foo at 0x10a436030, file "<stdin>", line 2>
) mà đã được biên soạn.
Chúng ta hãy kiểm tra đối tượng mã tạo ra thân lớp; các đối tượng mã có co_consts
cấu trúc:
>>> foo.__code__.co_consts
(None, <code object Foo at 0x10a436030, file "<stdin>", line 2>, 'Foo')
>>> dis.dis(foo.__code__.co_consts[1])
2 0 LOAD_FAST 0 (__locals__)
3 STORE_LOCALS
4 LOAD_NAME 0 (__name__)
7 STORE_NAME 1 (__module__)
10 LOAD_CONST 0 ('foo.<locals>.Foo')
13 STORE_NAME 2 (__qualname__)
3 16 LOAD_CONST 1 (5)
19 STORE_NAME 3 (x)
4 22 LOAD_CONST 2 (<code object <listcomp> at 0x10a385420, file "<stdin>", line 4>)
25 LOAD_CONST 3 ('foo.<locals>.Foo.<listcomp>')
28 MAKE_FUNCTION 0
31 LOAD_NAME 4 (range)
34 LOAD_CONST 4 (1)
37 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
40 GET_ITER
41 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
44 STORE_NAME 5 (y)
47 LOAD_CONST 5 (None)
50 RETURN_VALUE
Mã byte trên tạo ra lớp cơ thể. Hàm được thực thi và locals()
không gian tên kết quả , chứa x
và y
được sử dụng để tạo lớp (ngoại trừ việc nó không hoạt động vì x
không được định nghĩa là toàn cục). Lưu ý rằng sau khi lưu trữ 5
trong x
, nó tải một đối tượng mã; đó là sự hiểu biết danh sách; nó được bọc trong một đối tượng hàm giống như thân lớp; hàm được tạo có một đối số vị trí, range(1)
iterable để sử dụng cho mã lặp của nó, được truyền tới một iterator. Như được hiển thị trong mã byte, range(1)
được đánh giá trong phạm vi lớp.
Từ đó, bạn có thể thấy rằng sự khác biệt duy nhất giữa một đối tượng mã cho hàm hoặc trình tạo và đối tượng mã để hiểu là cái sau được thực thi ngay lập tức khi đối tượng mã cha mẹ được thực thi; mã byte chỉ đơn giản là tạo một hàm một cách nhanh chóng và thực hiện nó trong một vài bước nhỏ.
Python 2.x sử dụng mã byte nội tuyến ở đó thay vào đó, đây là đầu ra từ Python 2.7:
2 0 LOAD_NAME 0 (__name__)
3 STORE_NAME 1 (__module__)
3 6 LOAD_CONST 0 (5)
9 STORE_NAME 2 (x)
4 12 BUILD_LIST 0
15 LOAD_NAME 3 (range)
18 LOAD_CONST 1 (1)
21 CALL_FUNCTION 1
24 GET_ITER
>> 25 FOR_ITER 12 (to 40)
28 STORE_NAME 4 (i)
31 LOAD_NAME 2 (x)
34 LIST_APPEND 2
37 JUMP_ABSOLUTE 25
>> 40 STORE_NAME 5 (y)
43 LOAD_LOCALS
44 RETURN_VALUE
Không có đối tượng mã được tải, thay vào đó một FOR_ITER
vòng lặp được chạy nội tuyến. Vì vậy, trong Python 3.x, trình tạo danh sách đã được cung cấp một đối tượng mã phù hợp của riêng nó, có nghĩa là nó có phạm vi riêng.
Tuy nhiên, sự hiểu được được biên dịch cùng với phần còn lại của mã nguồn python khi mô-đun hoặc tập lệnh được trình thông dịch tải lần đầu tiên và trình biên dịch không coi bộ lớp là phạm vi hợp lệ. Bất kỳ biến tham chiếu nào trong việc hiểu danh sách phải nhìn trong phạm vi xung quanh định nghĩa lớp, theo cách đệ quy. Nếu biến không được trình biên dịch tìm thấy, nó sẽ đánh dấu nó là toàn cục. Việc tháo gỡ đối tượng mã hiểu danh sách cho thấy x
thực sự được tải dưới dạng toàn cục:
>>> foo.__code__.co_consts[1].co_consts
('foo.<locals>.Foo', 5, <code object <listcomp> at 0x10a385420, file "<stdin>", line 4>, 'foo.<locals>.Foo.<listcomp>', 1, None)
>>> dis.dis(foo.__code__.co_consts[1].co_consts[2])
4 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_GLOBAL 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
Đoạn mã byte này tải đối số đầu tiên được truyền vào ( range(1)
trình lặp) và giống như phiên bản Python 2.x sử dụng FOR_ITER
để lặp qua nó và tạo đầu ra của nó.
Nếu chúng ta đã định nghĩa x
trong foo
hàm thay vào đó, x
sẽ là một biến ô (các ô tham chiếu đến phạm vi lồng nhau):
>>> def foo():
... x = 2
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo.__code__.co_consts[2].co_consts[2])
5 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_DEREF 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
Các LOAD_DEREF
sẽ gián tiếp nạp x
từ các đối tượng tế bào đối tượng mã:
>>> foo.__code__.co_cellvars # foo function `x`
('x',)
>>> foo.__code__.co_consts[2].co_cellvars # Foo class, no cell variables
()
>>> foo.__code__.co_consts[2].co_consts[2].co_freevars # Refers to `x` in foo
('x',)
>>> foo().y
[2]
Tham chiếu thực tế trông giá trị tăng từ các cấu trúc dữ liệu khung hiện tại, được khởi tạo từ .__closure__
thuộc tính của đối tượng hàm . Vì hàm được tạo cho đối tượng mã hiểu được loại bỏ một lần nữa, chúng ta không thể kiểm tra sự đóng của hàm đó. Để thấy sự đóng cửa đang hoạt động, chúng ta phải kiểm tra một hàm lồng nhau:
>>> def spam(x):
... def eggs():
... return x
... return eggs
...
>>> spam(1).__code__.co_freevars
('x',)
>>> spam(1)()
1
>>> spam(1).__closure__
>>> spam(1).__closure__[0].cell_contents
1
>>> spam(5).__closure__[0].cell_contents
5
Vì vậy, để tóm tắt:
- Việc hiểu danh sách có được các đối tượng mã riêng của họ trong Python 3 và không có sự khác biệt giữa các đối tượng mã cho các hàm, trình tạo hoặc hiểu; các đối tượng mã hiểu được gói trong một đối tượng hàm tạm thời và được gọi ngay lập tức.
- Các đối tượng mã được tạo tại thời điểm biên dịch và bất kỳ biến không cục bộ nào được đánh dấu là biến toàn cục hoặc biến tự do, dựa trên phạm vi lồng nhau của mã. Phần thân lớp không được coi là một phạm vi để tìm kiếm các biến đó.
- Khi thực thi mã, Python chỉ phải nhìn vào toàn cục hoặc đóng đối tượng hiện đang thực thi. Do trình biên dịch không bao gồm phần thân lớp làm phạm vi, nên không gian tên hàm tạm thời không được xem xét.
Một cách giải quyết; hoặc, phải làm gì về nó
Nếu bạn đã tạo một phạm vi rõ ràng cho x
biến, như trong một hàm, bạn có thể sử dụng các biến phạm vi lớp để hiểu danh sách:
>>> class Foo:
... x = 5
... def y(x):
... return [x for i in range(1)]
... y = y(x)
...
>>> Foo.y
[5]
Hàm 'tạm thời' y
có thể được gọi trực tiếp; chúng tôi thay thế nó khi chúng tôi làm với giá trị trả lại của nó. Phạm vi của nó được xem xét khi giải quyết x
:
>>> foo.__code__.co_consts[1].co_consts[2]
<code object y at 0x10a5df5d0, file "<stdin>", line 4>
>>> foo.__code__.co_consts[1].co_consts[2].co_cellvars
('x',)
Tất nhiên, những người đọc mã của bạn sẽ gãi đầu về điều này một chút; bạn có thể muốn đưa ra một nhận xét lớn về chất béo để giải thích lý do tại sao bạn làm điều này.
Cách giải quyết tốt nhất là chỉ sử dụng __init__
để tạo một biến thể hiện thay thế:
def __init__(self):
self.y = [self.x for i in range(1)]
và tránh tất cả các câu hỏi đầu, và câu hỏi để giải thích chính mình. Đối với ví dụ cụ thể của riêng bạn, tôi thậm chí sẽ không lưu trữ namedtuple
trên lớp; hoặc sử dụng trực tiếp đầu ra (hoàn toàn không lưu trữ lớp đã tạo) hoặc sử dụng toàn cục:
from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])
class StateDatabase:
db = [State(*args) for args in [
('Alabama', 'Montgomery'),
('Alaska', 'Juneau'),
# ...
]]
NameError: global name 'x' is not defined
trên Python 3.2 và 3.3, đó là những gì tôi mong đợi.