2009-11-20 2 views
15

Как именно Python оценивает атрибуты класса? Я наткнулся на интересную причуду (в Python 2.5.2), которую я хотел бы объяснить.Оценка атрибутов класса и генераторы

У меня есть класс с некоторыми атрибутами, которые определены в терминах других ранее определенных атрибутов. Когда я пытаюсь использовать объект-генератор, Python выдает ошибку, но если я использую простое обычное понимание списка, нет никаких проблем.

Вот пример урезанного. Обратите внимание, что единственная разница заключается в том, что Brie использует выражение генератора, а Cheddar использует понимание списка.

# 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] 

(мой фактический случай был более сложным, и я создавал Dict, но это минимальный пример, который я смог найти.)

Моя только догадка, что списочные вычисляются на этой линии , но выражения генератора вычисляются после окончания класса, после чего область изменений изменилась. Но я не уверен, почему выражение генератора не действует как замыкание и сохраняет ссылку на базу в области на линии.

Есть ли причина для этого, и если да, то как я должен думать о механизме оценки атрибутов класса?

ответ

14

Да, это немного изворотливо, это. Класс действительно не вводит новую область, это просто выглядит немного похоже на это; такие конструкции раскрывают разницу.

Идея заключается в том, что, когда вы используете выражение генератора это эквивалентно делать это с лямбда:

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

или явно как функция заявления:

class Brie(object): 
    base= 2 

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

    powers= list(__generatePowers()) 

В данном случае это ясно, что base не подходит для __generatePowers; результаты исключения для обоих (если только вам не повезло, чтобы иметь также base глобальный, и в этом случае вы ошибаетесь).

Это не происходит для понимания списков из-за некоторых внутренних подробностей о том, как они оцениваются, однако это поведение уходит в Python 3, что в обоих случаях не будет одинаковым. Some discussion here.

обходной путь можно было с помощью лямбда с той же техникой, мы полагались на спине в старые времена, когда nested_scopes:

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

От PEP 289:

После изучения многочисленных возможностей, a Консенсус показал, что проблемы с привязкой были непонятными и что пользователям следует настоятельно рекомендовать использовать выражения генератора внутри функции , которые уничтожают свои аргументы немедленно. Для более сложных применений, полный генератор определения всегда превосходят по условия очевидности сферы, срок службы и привязку [6].

[6] (1, 2) патч обсуждение и альтернативные пятна на Source Forge http://www.python.org/sf/872326

Это как выражения генератора находятся в области видимости, насколько я могу разобрать.

Смежные вопросы