Обсяг та список класів, розуміння набору чи словника, а також вирази генератора не змішуються.
Чому; або, офіційне слово з цього приводу
У Python 3 розуміння списків отримали належну область власного простору (локальний простір імен), щоб не допустити, щоб їх локальні змінні перетікали в навколишнє поле (див. Див. Пересвідчення імен списку Python навіть після сфери розуміння. Це правильно? ). Це чудово, коли використовується таке розуміння списку в модулі або у функції, але в класах, визначення обсягу - це трохи, ем, дивно .
Це задокументовано в pep 227 :
Назви в межах класу недоступні. Імена розв’язуються в найпотужнішій області застосування функції. Якщо визначення класу відбувається в ланцюжку вкладених областей, процес роздільної здатності пропускає визначення класів.
та у class
складеній документації на виписку :
Потім набір класів виконується в новому кадрі виконання (див. Розділ Іменування та прив'язка ), використовуючи щойно створений локальний простір імен та оригінальний глобальний простір імен. (Зазвичай пакет містить лише визначення функцій.) Коли набір класу закінчує виконання, його рамка виконання відкидається, але локальне місце імен зберігається . [4] Потім об’єкт класу створюється за допомогою списку спадкування для базових класів та збереженого локального простору імен для словника атрибутів.
Наголос мій; кадр виконання - це тимчасова область.
Оскільки сфера області перестановляється як атрибути на об'єкт класу, що дозволяє використовувати його як нелокальну область, а також призводить до невизначеної поведінки; що трапиться, якщо метод класу, який називається x
вкладеною змінною області, потім маніпулює Foo.x
також, наприклад? Що ще важливіше, що це буде означати для підкласів Foo
? Python має для лікування рамки класу по- різному , як це дуже відрізняється від області видимості функції.
І останнє, але, безумовно, не менш важливе, в зв'язаному розділі Іменування та прив'язки в документації про модель виконання чітко згадуються сфери застосування класу:
Область імен, визначених у блоці класів, обмежена блоком класів; він не поширюється на кодові блоки методів - це включає розуміння та вирази генератора, оскільки вони реалізовані за допомогою функції функції. Це означає, що не вдасться:
class A:
a = 42
b = list(a + i for i in range(10))
Отже, підсумовуючи: ви не можете отримати доступ до області класу з функцій, розуміння списків або виразів генератора, що додаються до цієї області; вони діють так, ніби цього сфери не існує. У Python 2 розуміння списків було реалізовано за допомогою ярлика, але в Python 3 вони отримали власну область функцій (як це повинно було бути в усьому світі), і таким чином ваш приклад порушується. Інші типи розуміння мають власну сферу застосування незалежно від версії Python, тому подібний приклад із розумінням набору чи диктату порушиться у Python 2.
# Same error, in Python 2 or 3
y = {x: x for i in range(1)}
(Невеликий) виняток; або, чому одна частина може ще працювати
Є одна частина розуміння або генераторного вираження, яка виконується в навколишньому просторі, незалежно від версії Python. Це було б вираженням для найбільш ітерабельного. У вашому прикладі це range(1)
:
y = [x for i in range(1)]
# ^^^^^^^^
Таким чином, використання x
в цьому виразі не призведе до помилки:
# Runs fine
y = [i for i in range(x)]
Це стосується лише самого зовнішнього ітерабельного; якщо розуміння має декілька for
застережень, ітерабелі внутрішніх for
пропозицій оцінюються в межах розуміння:
# NameError
y = [i for i in range(1) for j in range(x)]
Це дизайнерське рішення було прийнято для того, щоб скинути помилку під час створення genexp замість часу ітерації, коли створюється самий зовнішній ітерабельний вираз генератора. Порозуміння поділяють цю поведінку для послідовності.
Дивлячись під капюшон; або, більш детально, ніж ви коли-небудь хотіли
Це все ви можете бачити за допомогою dis
модуля . Я використовую Python 3.3 у наступних прикладах, оскільки він додає кваліфіковані імена, які чітко ідентифікують об'єкти коду, які ми хочемо перевірити. В іншому випадку створений байт-код функціонально ідентичний Python 3.2.
Щоб створити клас, Python по суті бере весь набір, який складається з класу тіла (тому все з відступом на один рівень глибше class <name>:
лінії), і виконує це, ніби це функція:
>>> import dis
>>> def foo():
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo)
2 0 LOAD_BUILD_CLASS
1 LOAD_CONST 1 (<code object Foo at 0x10a436030, file "<stdin>", line 2>)
4 LOAD_CONST 2 ('Foo')
7 MAKE_FUNCTION 0
10 LOAD_CONST 2 ('Foo')
13 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
16 STORE_FAST 0 (Foo)
5 19 LOAD_FAST 0 (Foo)
22 RETURN_VALUE
Перший LOAD_CONST
завантажує об'єкт коду для Foo
тіла класу, потім робить це у функції і викликає його. Результат цього виклику використовується для створення простору імен класу, його __dict__
. Все йде нормально.
Тут слід зазначити, що байт-код містить вкладений об'єкт коду; У Python визначення класів, функції, розуміння та генератори представлені у вигляді об'єктів коду, що містять не тільки байтовий код, але й структури, що представляють локальні змінні, константи, змінні, взяті з глобалів, та змінні, взяті з вкладеної області. Скомпільований байт-код відноситься до цих структур, і інтерпретатор python знає, як отримати доступ до тих, що даються представленими байткодами.
Тут важливо пам’ятати, що Python створює ці структури під час компіляції; class
люкс код об'єкта ( <code object Foo at 0x10a436030, file "<stdin>", line 2>
) , який вже складений.
Давайте перевіримо той об'єкт коду, який створює сам клас класу; об'єкти коду мають co_consts
структуру:
>>> foo.__code__.co_consts
(None, <code object Foo at 0x10a436030, file "<stdin>", line 2>, 'Foo')
>>> dis.dis(foo.__code__.co_consts[1])
2 0 LOAD_FAST 0 (__locals__)
3 STORE_LOCALS
4 LOAD_NAME 0 (__name__)
7 STORE_NAME 1 (__module__)
10 LOAD_CONST 0 ('foo.<locals>.Foo')
13 STORE_NAME 2 (__qualname__)
3 16 LOAD_CONST 1 (5)
19 STORE_NAME 3 (x)
4 22 LOAD_CONST 2 (<code object <listcomp> at 0x10a385420, file "<stdin>", line 4>)
25 LOAD_CONST 3 ('foo.<locals>.Foo.<listcomp>')
28 MAKE_FUNCTION 0
31 LOAD_NAME 4 (range)
34 LOAD_CONST 4 (1)
37 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
40 GET_ITER
41 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
44 STORE_NAME 5 (y)
47 LOAD_CONST 5 (None)
50 RETURN_VALUE
Вищенаведений байт-код створює тіло класу. Функція виконується, а отриманий locals()
простір імен, що містить x
і y
використовується для створення класу (за винятком того, що він не працює, оскільки x
не визначається як глобальний). Зверніть увагу , що після зберігання 5
в x
він завантажує інший код об'єкта; ось розуміння списку; він загорнутий у об’єкт функції так само, як це було тело класу; створена функція приймає позиційний аргумент, range(1)
ітерабельний, який слід використовувати для свого циклу циклу, переданий ітератору. Як показано в байтовому коді, range(1)
оцінюється в області класу.
З цього видно, що єдина відмінність між кодовим об'єктом функції або генератора та кодовим об'єктом для розуміння полягає в тому, що останній виконується негайно при виконанні батьківського кодового об'єкта; байт-код просто створює функцію на льоту і виконує її в кілька невеликих кроків.
Python 2.x замість цього використовує вбудований байт-код, тут виводиться з Python 2.7:
2 0 LOAD_NAME 0 (__name__)
3 STORE_NAME 1 (__module__)
3 6 LOAD_CONST 0 (5)
9 STORE_NAME 2 (x)
4 12 BUILD_LIST 0
15 LOAD_NAME 3 (range)
18 LOAD_CONST 1 (1)
21 CALL_FUNCTION 1
24 GET_ITER
>> 25 FOR_ITER 12 (to 40)
28 STORE_NAME 4 (i)
31 LOAD_NAME 2 (x)
34 LIST_APPEND 2
37 JUMP_ABSOLUTE 25
>> 40 STORE_NAME 5 (y)
43 LOAD_LOCALS
44 RETURN_VALUE
Не завантажується жоден об'єкт коду, натомість FOR_ITER
цикл запускається в рядок. Так, в Python 3.x, генератору списку було надано власний об'єкт коду, що означає, що він має власну сферу застосування.
Однак розуміння було складено разом з рештою вихідного коду python, коли модуль або скрипт був вперше завантажений інтерпретатором, і компілятор не вважає набір класів допустимим областю. Будь-які згадані змінні в розумінні списку повинні шукати реферативну область, що оточує визначення класу. Якщо компілятор не знайшов змінну, вона позначає її як глобальну. Розбирання об'єкта коду розуміння списку показує, що x
дійсно завантажується як глобальний:
>>> foo.__code__.co_consts[1].co_consts
('foo.<locals>.Foo', 5, <code object <listcomp> at 0x10a385420, file "<stdin>", line 4>, 'foo.<locals>.Foo.<listcomp>', 1, None)
>>> dis.dis(foo.__code__.co_consts[1].co_consts[2])
4 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_GLOBAL 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
Цей шматок байт-коду завантажує перший аргумент, переданий в ( range(1)
ітератор), і так само, як версія Python 2.x використовує FOR_ITER
для переведення циклу на нього та створення його результату.
Якби ми замість цього визначилися x
у foo
функції, x
було б змінною комірки (комірки посилаються на вкладені області застосування):
>>> def foo():
... x = 2
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo.__code__.co_consts[2].co_consts[2])
5 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_DEREF 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
LOAD_DEREF
Побічно завантажувати x
з об'єктів об'єктного коду осередки:
>>> foo.__code__.co_cellvars # foo function `x`
('x',)
>>> foo.__code__.co_consts[2].co_cellvars # Foo class, no cell variables
()
>>> foo.__code__.co_consts[2].co_consts[2].co_freevars # Refers to `x` in foo
('x',)
>>> foo().y
[2]
Фактичне посилання виглядає значенням з поточних структур даних кадру, які були ініціалізовані з .__closure__
атрибуту об’єкта функції . Оскільки функція, створена для об'єкта коду розуміння, знову відкидається, ми не отримуємо перевірки закриття цієї функції. Щоб побачити закриття в дії, нам доведеться перевірити вкладену функцію:
>>> def spam(x):
... def eggs():
... return x
... return eggs
...
>>> spam(1).__code__.co_freevars
('x',)
>>> spam(1)()
1
>>> spam(1).__closure__
>>> spam(1).__closure__[0].cell_contents
1
>>> spam(5).__closure__[0].cell_contents
5
Отже, підсумовуючи:
- Зрозуміння списків отримують власні кодові об'єкти в Python 3, і різниці між кодовими об'єктами для функцій, генераторів чи розумінь немає; Об'єкти коду розуміння загортаються у тимчасовий об'єкт функції та викликаються негайно.
- Об'єкти коду створюються під час компіляції, і будь-які не локальні змінні позначаються як глобальні, або як вільні змінні, на основі вкладених областей коду. Орган класу не вважається простором для пошуку цих змінних.
- Виконуючи код, Python повинен лише заглянути до глобальних точок чи закриття поточного виконуючого об'єкта. Оскільки компілятор не включав тіло класу як область, тимчасова область імен функції не враховується.
Обхідний шлях; або, що з цим робити
Якщо ви повинні створити явну область для x
змінної, як у функції, ви можете використовувати змінні класу-області для розуміння списку:
>>> class Foo:
... x = 5
... def y(x):
... return [x for i in range(1)]
... y = y(x)
...
>>> Foo.y
[5]
Функцію "тимчасової" y
можна викликати безпосередньо; ми замінюємо його, коли ми робимо його повернене значення. Його сфера буде враховуватися при вирішенні x
:
>>> foo.__code__.co_consts[1].co_consts[2]
<code object y at 0x10a5df5d0, file "<stdin>", line 4>
>>> foo.__code__.co_consts[1].co_consts[2].co_cellvars
('x',)
Звичайно, люди, які читають ваш код, трохи почухають голову над цим; ви можете покласти там великий коментар, який пояснює, чому ви це робите.
Найкраще обійти це просто використовувати __init__
для створення змінної екземпляра замість цього:
def __init__(self):
self.y = [self.x for i in range(1)]
і уникайте всіх пошкоджень голови та питань, щоб пояснити себе. Для вашого власного конкретного прикладу я навіть не зберігав би namedtuple
цей клас; або використовувати вихід безпосередньо (не зберігати згенерований клас взагалі), або використовувати глобальний:
from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])
class StateDatabase:
db = [State(*args) for args in [
('Alabama', 'Montgomery'),
('Alaska', 'Juneau'),
# ...
]]
NameError: global name 'x' is not defined
на Python 3.2 та 3.3, що я б очікував.