Оценка атрибутов класса и генераторы

Как именно 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]

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

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

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

2 ответа

Решение

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

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

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, что в обоих случаях не сработает. Некоторое обсуждение здесь.

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

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

От PEP 289:

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

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

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

Другие вопросы по тегам