Оценка атрибутов класса и генераторы
Как именно 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
Это, как я могу разобрать, выражения генератора.