Вопрос

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

Другие советы

От БОДРОСТЬ ДУХА 289:

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top