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