Оценка атрибутов класса и генераторов
Как именно 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, но это минимальный пример, который я мог найти.)
Мое единственное предположение состоит в том, что методы подсчета списков вычисляются в этой строке, но выражения генератора вычисляются после окончания класса, после чего область изменений изменилась. Но я не уверен, почему выражение генератора не действует как замыкание и сохраняет ссылку на базу в области на линии.
Есть ли причина для этого, и если да, то как я должен думать о механизме оценки атрибутов класса?
Ответы
Ответ 1
Да, это немного изворотливо. Класс действительно не вводит новую область, это просто выглядит немного похоже на это; конструкции, подобные этому, раскрывают разницу.
Идея состоит в том, что, когда вы используете выражение генератора, это эквивалентно его использованию с помощью лямбда:
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
global, и в этом случае вы получите ошибку).
Это не происходит для понимания списков из-за некоторых внутренних деталей о том, как они оцениваются, однако это поведение исчезает в Python 3, что в обоих случаях не будет одинаковым. Некоторые обсуждения здесь.
Обходной путь можно использовать с помощью лямбда с той же техникой, на которую мы полагались назад в плохие старые дни перед nested_scopes:
class Brie(object):
base= 2
powers= map(lambda i, base= base: base**i, xrange(5))
Ответ 2
От PEP 289:
После изучения многих возможностей консенсус показал, что обязательные вопросы было трудно понять и что пользователи следует настоятельно рекомендовать использовать генераторные выражения внутри функций которые потребляют свои аргументы немедленно. Для более сложных приложения, полный генератор определения всегда превосходят условия очевидности сферы, времени жизни и привязки [6].
[6] (1, 2) Обсуждение патчей и альтернативные патчи на Source Forge http://www.python.org/sf/872326
Как показано выражения генератора, насколько я могу это понять.