Truy cập các biến lớp từ một sự hiểu biết danh sách trong định nghĩa lớp


174

Làm thế nào để bạn truy cập các biến lớp khác từ một sự hiểu biết danh sách trong định nghĩa lớp? Các hoạt động sau đây trong Python 2 nhưng không thành công trong Python 3:

class Foo:
    x = 5
    y = [x for i in range(1)]

Python 3.2 báo lỗi:

NameError: global name 'x' is not defined

Cố gắng Foo.xcũng không làm việc. Bất kỳ ý tưởng về cách làm điều này trong Python 3?

Một ví dụ thúc đẩy phức tạp hơn một chút:

from collections import namedtuple
class StateDatabase:
    State = namedtuple('State', ['name', 'capital'])
    db = [State(*args) for args in [
        ['Alabama', 'Montgomery'],
        ['Alaska', 'Juneau'],
        # ...
    ]]

Trong ví dụ này, apply()sẽ là một cách giải quyết tốt, nhưng nó đáng buồn bị xóa khỏi Python 3.


Thông báo lỗi của bạn không chính xác. Tôi nhận được NameError: global name 'x' is not definedtrên Python 3.2 và 3.3, đó là những gì tôi mong đợi.
Martijn Pieters

Thú vị ... Một cách giải quyết rõ ràng là gán y sau khi bạn thoát khỏi định nghĩa lớp. Foo.y = [Foo.x cho i trong phạm vi (1)]
gps

3
+ martijn-pieters liên kết đến một bản sao là đúng, có một nhận xét từ + matt-b trong đó với lời giải thích: Việc hiểu danh sách Python 2.7 không có không gian tên riêng của chúng (không giống như hiểu hoặc đặt chính tả hoặc biểu thức trình tạo ... thay thế [ ] với {} để thấy rằng trong hành động). Tất cả đều có không gian tên riêng trong 3.
gps

@gps: Hoặc sử dụng phạm vi lồng nhau, bằng cách chèn một hàm (tạm thời) trong bộ định nghĩa lớp.
Martijn Pieters

Tôi vừa thử nghiệm vào ngày 2.7.11. Có lỗi tên
Junchao Gu

Câu trả lời:


244

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 classtà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 xlà 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 để đ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 xtrong 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 formệnh đề, các lần lặp cho forcá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 dismô-đ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 Foothâ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 classbộ 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_constscấ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 xyđược sử dụng để tạo lớp (ngoại trừ việc nó không hoạt động vì xkhông được định nghĩa là toàn cục). Lưu ý rằng sau khi lưu trữ 5trong 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_ITERvò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 xthự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 xtrong foohàm thay vào đó, xsẽ 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_DEREFsẽ gián tiếp nạp xtừ 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 xbiế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' ycó 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ữ namedtupletrê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'),
       # ...
    ]]

21
Bạn cũng có thể sử dụng lambda để sửa lỗi ràng buộc:y = (lambda x=x: [x for i in range(1)])()
ecatmur

3
@ecatmur: Chính xác, lambdachỉ là các chức năng ẩn danh.
Martijn Pieters

2
Đối với bản ghi, công việc xung quanh sử dụng một đối số mặc định (cho lambda hoặc hàm) để truyền vào biến lớp có một mã xác thực. Cụ thể, nó vượt qua giá trị hiện tại của biến. Vì vậy, nếu biến thay đổi sau đó, và sau đó lambda hoặc hàm được gọi, lambda hoặc hàm sẽ sử dụng giá trị cũ. Hành vi này khác với hành vi của một bao đóng (sẽ thu được một tham chiếu đến biến, thay vì giá trị của nó), do đó có thể là bất ngờ.
Neal Young

9
Nếu nó yêu cầu một trang thông tin kỹ thuật để giải thích lý do tại sao một cái gì đó không hoạt động bằng trực giác, tôi gọi đó là một lỗi.
Jonathan

5
@JonathanLeaders: Đừng gọi đó là lỗi , hãy gọi đó là sự đánh đổi . Nếu bạn muốn A và B, nhưng chỉ có thể nhận được một trong số họ, thì dù bạn quyết định thế nào, trong một số tình huống bạn sẽ không thích kết quả. Đó là cuộc sống.
Lutz Prechelt

15

Theo tôi đó là một lỗ hổng trong Python 3. Tôi hy vọng họ thay đổi nó.

Old Way (hoạt động trong 2.7, ném NameError: name 'x' is not definedtrong 3+):

class A:
    x = 4
    y = [x+i for i in range(1)]

LƯU Ý: chỉ đơn giản là phạm vi nó A.xsẽ không giải quyết nó

Cách mới (hoạt động trong 3+):

class A:
    x = 4
    y = (lambda x=x: [x+i for i in range(1)])()

Bởi vì cú pháp rất xấu, tôi chỉ khởi tạo tất cả các biến lớp của mình trong hàm tạo


6
Vấn đề cũng có trong Python 2, khi sử dụng các biểu thức của trình tạo, cũng như với sự hiểu biết về tập hợp và từ điển. Nó không phải là một lỗi, nó là hậu quả của cách các không gian tên lớp hoạt động. Nó sẽ không thay đổi.
Martijn Pieters

4
Và tôi lưu ý rằng cách giải quyết của bạn thực hiện chính xác những gì câu trả lời của tôi đã nêu: tạo một phạm vi mới (lambda không khác ở đây với việc sử dụng defđể tạo hàm).
Martijn Pieters

1
Vâng. Mặc dù rất vui khi có câu trả lời với công việc xung quanh trong nháy mắt, nhưng điều này không chính xác nêu hành vi là một lỗi, khi đó là tác dụng phụ của cách ngôn ngữ hoạt động (và do đó, sẽ không bị thay đổi)
jsbueno

Đây là một vấn đề khác, thực sự không phải là vấn đề trong Python 3. Nó chỉ xảy ra trong IPython khi bạn gọi nó trong chế độ nhúng bằng cách sử dụng say python -c "import IPython;IPython.embed()". Chạy IPython trực tiếp bằng cách sử dụng nói ipythonvà vấn đề sẽ biến mất.
Riaz Rizvi

6

Câu trả lời được chấp nhận cung cấp thông tin tuyệt vời, nhưng dường như có một vài nếp nhăn khác ở đây - sự khác biệt giữa hiểu danh sách và biểu thức trình tạo. Một bản demo mà tôi đã chơi xung quanh:

class Foo:

    # A class-level variable.
    X = 10

    # I can use that variable to define another class-level variable.
    Y = sum((X, X))

    # Works in Python 2, but not 3.
    # In Python 3, list comprehensions were given their own scope.
    try:
        Z1 = sum([X for _ in range(3)])
    except NameError:
        Z1 = None

    # Fails in both.
    # Apparently, generator expressions (that's what the entire argument
    # to sum() is) did have their own scope even in Python 2.
    try:
        Z2 = sum(X for _ in range(3))
    except NameError:
        Z2 = None

    # Workaround: put the computation in lambda or def.
    compute_z3 = lambda val: sum(val for _ in range(3))

    # Then use that function.
    Z3 = compute_z3(X)

    # Also worth noting: here I can refer to XS in the for-part of the
    # generator expression (Z4 works), but I cannot refer to XS in the
    # inner-part of the generator expression (Z5 fails).
    XS = [15, 15, 15, 15]
    Z4 = sum(val for val in XS)
    try:
        Z5 = sum(XS[i] for i in range(len(XS)))
    except NameError:
        Z5 = None

print(Foo.Z1, Foo.Z2, Foo.Z3, Foo.Z4, Foo.Z5)

2

Đây là một lỗi trong Python. Hiểu được quảng cáo là tương đương với các vòng lặp, nhưng điều này không đúng trong các lớp học. Ít nhất lên tới Python 3.6.6, trong một sự hiểu biết được sử dụng trong một lớp, chỉ có một biến từ bên ngoài sự hiểu biết có thể truy cập được bên trong sự hiểu biết và nó phải được sử dụng như là trình vòng lặp ngoài cùng. Trong một chức năng, giới hạn phạm vi này không áp dụng.

Để minh họa tại sao đây là một lỗi, hãy quay lại ví dụ ban đầu. Điều này không thành công:

class Foo:
    x = 5
    y = [x for i in range(1)]

Nhưng điều này hoạt động:

def Foo():
    x = 5
    y = [x for i in range(1)]

Giới hạn được nêu ở cuối phần này trong hướng dẫn tham khảo.


1

Vì trình vòng lặp ngoài cùng được đánh giá trong phạm vi xung quanh, chúng ta có thể sử dụng zipcùng với itertools.repeatđể mang các phụ thuộc qua phạm vi hiểu:

import itertools as it

class Foo:
    x = 5
    y = [j for i, j in zip(range(3), it.repeat(x))]

Người ta cũng có thể sử dụng các forvòng lặp lồng nhau trong sự hiểu và bao gồm các phụ thuộc trong lần lặp ngoài cùng:

class Foo:
    x = 5
    y = [j for j in (x,) for i in range(3)]

Đối với ví dụ cụ thể của OP:

from collections import namedtuple
import itertools as it

class StateDatabase:
    State = namedtuple('State', ['name', 'capital'])
    db = [State(*args) for State, args in zip(it.repeat(State), [
        ['Alabama', 'Montgomery'],
        ['Alaska', 'Juneau'],
        # ...
    ])]
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.