2009-11-20 29 views
15

Python đánh giá chính xác các thuộc tính lớp như thế nào? Tôi đã tình cờ gặp một điều thú vị (trong Python 2.5.2) mà tôi muốn giải thích.Đánh giá và tạo thuộc tính lớp

Tôi có một lớp học với một số thuộc tính được xác định theo các thuộc tính khác, được xác định trước đó. Khi tôi cố gắng sử dụng một đối tượng máy phát điện, Python ném một lỗi, nhưng nếu tôi sử dụng một danh sách đồng bằng thông thường, thì không có vấn đề gì.

Đây là ví dụ minh họa. Lưu ý rằng sự khác biệt duy nhất là Brie sử dụng biểu thức trình tạo, trong khi Cheddar sử dụng tính năng hiểu danh sách.

# Using a generator expression as the argument to list() fails 
>>> class Brie : 
...  base = 2 
...  powers = list(base**i for i in xrange(5)) 
... 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 3, in Brie 
    File "<stdin>", line 3, in <genexpr> 
NameError: global name 'base' is not defined 

# Using a list comprehension works 
>>> class Cheddar : 
...  base = 2 
...  powers = [base**i for i in xrange(5)] 
... 
>>> Cheddar.powers 
[1, 2, 4, 8, 16] 

# Using a list comprehension as the argument to list() works 
>>> class Edam : 
...  base = 2 
...  powers = list([base**i for i in xrange(5)]) 
... 
>>> Edam.powers 
[1, 2, 4, 8, 16] 

(trường hợp thực tế của tôi là phức tạp hơn, và tôi đã tạo ra một dict, nhưng đây là ví dụ tối thiểu tôi có thể tìm thấy.)

đoán duy nhất của tôi là rằng comprehensions danh sách được tính tại dòng đó , nhưng các biểu thức máy phát điện được tính sau khi kết thúc lớp, tại thời điểm đó phạm vi đã thay đổi. Nhưng tôi không chắc tại sao biểu thức máy phát không hoạt động như một đóng và lưu trữ tham chiếu đến cơ sở trong phạm vi tại dòng.

Có lý do nào cho điều này không, và nếu có, tôi nên suy nghĩ như thế nào về cơ chế đánh giá thuộc tính lớp?

Trả lời

14

Vâng, có một chút tinh ranh, điều này. Một lớp không thực sự giới thiệu một phạm vi mới, nó chỉ trông hơi giống như nó; các cấu trúc như thế này cho thấy sự khác biệt.

Ý tưởng là khi bạn đang sử dụng một biểu thức máy phát điện đó là tương đương với làm việc đó với một lambda:

class Brie(object): 
    base= 2 
    powers= map(lambda i: base**i, xrange(5)) 

hoặc một cách rõ ràng như một tuyên bố chức năng:

class Brie(object): 
    base= 2 

    def __generatePowers(): 
     for i in xrange(5): 
      yield base**i 

    powers= list(__generatePowers()) 

Trong trường hợp này đó là rõ ràng rằng base không nằm trong phạm vi cho __generatePowers; một kết quả ngoại lệ cho cả hai (trừ khi bạn không đủ may mắn để có một số base toàn cầu, trong trường hợp này bạn nhận được một sự sai trái).

Điều này không xảy ra đối với việc hiểu danh sách do một số chi tiết nội bộ về cách chúng được đánh giá, tuy nhiên hành vi đó biến mất trong Python 3 sẽ không đồng đều cho cả hai trường hợp. Some discussion here.

Một cách giải quyết có thể đã sử dụng một lambda với kỹ thuật tương tự chúng tôi dựa vào trở lại trong những ngày cũ xấu trước nested_scopes:

class Brie(object): 
    base= 2 
    powers= map(lambda i, base= base: base**i, xrange(5)) 
1

Từ PEP 289:

Sau khi khám phá nhiều khả năng, một sự đồng thuận nổi lên rằng các vấn đề ràng buộc khó hiểu và rằng người dùng nên được khuyến khích mạnh mẽ để sử dụng biểu thức trình phát các hàm bên trong tiêu thụ đối số của chúng ngay lập tức. Đối với các ứng dụng phức tạp hơn , toàn bộ máy phát điện định nghĩa luôn vượt trội trong các điều khoản rõ ràng về phạm vi, tuổi thọ và liên kết [6].

[6] (1, 2) thảo luận Patch và thay thế bản vá lỗi trên Source Forge http://www.python.org/sf/872326

Đó là cách biểu hiện máy phát điện được scoped như xa như tôi có thể làm ra.

Các vấn đề liên quan