Назначение переменной класса Python с использованием словарного понимания - PullRequest
0 голосов
/ 13 ноября 2018

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

class C(object):
    ALL_ITEMS = dict(a='A', b='B', c='C', d='D', e='E')
    SUBSET_X = {k: v for k, v in ALL_ITEMS.items() if k in ('a', 'b', 'd')}  # (this works)
    SUBSET_Y = {k: ALL_ITEMS[k] for k in ('a', 'b', 'd')}  # (this fails)

Довольно простые вещи, но чистый эффект от выполнения этого кода меня довольно удивляет.Моим первым подходом был код в строке 4, но вместо этого мне пришлось прибегнуть к решению в строке 3.В правилах определения объема словаря есть что-то тонкое, что я явно не могу понять.

В частности, ошибка возникает в случае сбоя:

File "goofy.py", line 4, in <dictcomp>
   SUBSET_Y = {k: ALL_ITEMS.get(k) for k in ('a', 'b', 'd')}
NameError: name 'ALL_ITEMS' is not defined

Природа этой ошибкисбивает с толку меня по нескольким причинам:

  1. Задание для SUBSET_Y является правильно сформированным словарным пониманием и ссылается на символ, который должен быть внутри области видимости и доступен.
  2. В последующем случае (присвоение SUBSET_X), которое также является словарным пониманием, символ ALL_ITEMS является совершенно четким и доступным.Таким образом, тот факт, что возбужденное исключение является NameError в случае неудачи, представляется явно неверным.(Или, в лучшем случае, вводит в заблуждение.)
  3. Почему правила области видимости отличаются для items() против __getitem__ или get()?(Такое же исключение происходит при замене ALL_ITEMS[k] на ALL_ITEMS.get(k) в случае сбоя.)

(Даже более десяти лет назад, как разработчик Python, я никогда не сталкивался с этой ошибкой, котораялибо означает, что мне повезло, либо я жил скрытно: ^)

Один и тот же сбой происходит в различных версиях 3.6.x CPython, а также в версиях 2.7.x.

РЕДАКТИРОВАТЬ: НетЭто не дубликат предыдущего вопроса.Это относилось к списку пониманий, и даже если кто-то должен был проецировать одно и то же объяснение на словарь, это не объясняет разницу между двумя приведенными мною случаями.А также, это не феномен только для Python 3.

1 Ответ

0 голосов
/ 13 ноября 2018

Есть одна небольшая деталь, которая объясняет, почему работает первая версия, но вторая не работает. Причина, по которой вторая версия дает сбой, - это та же самая причина, которая дана в этом вопросе , а именно, все конструкции понимания (в Python 3, в Python 2 списочные понимания были реализованы по-разному) создают область действия функции, где все из местных имен привязок происходят. Однако имена в области видимости класса не доступны для функций, определенных в области видимости класса. Вот почему вы должны использовать self.MY_CLASS_VAR или MyClass.MY_CLASS_VAR для доступа к переменной класса из метода.

Причина, по которой ваш первый случай сработал, неуловима. Согласно языковой справке

Понимание состоит из одного выражения, за которым следует, по крайней мере, один для предложения и ноль или более для или если предложения. В этом случае элементы нового контейнера - это те, которые будут произведены с учетом каждого из пунктов for или if блока, вложенность слева вправо и вычисляя выражение для создания элемента каждый время достижения самого внутреннего блока.

Однако кроме итеративного выражения в крайнем левом углу для пункт, понимание выполняется в отдельном неявно вложенном scope. Это гарантирует, что имена, назначенные в списке целей, не будут «Утечка» в ограждающую область.

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

Таким образом, в первом случае ALL_ITEMS.items() находится в крайнем левом выражении for, поэтому вычисляется непосредственно во вложенной области видимости , в данном случае в области видимости класса, так что, к счастью, находит ALL_ITEMS имя.

...