Создание класса внутри функции и доступ к функции, определенной в области видимости содержащей функции - PullRequest
58 голосов
/ 28 ноября 2010

Редактировать :

Смотрите мой полный ответ внизу этого вопроса.

tl; dr answer : Python имеетстатически вложенные области видимости. static может взаимодействовать с неявными объявлениями переменных, давая неочевидные результаты.

(Это может быть особенно удивительно из-за общей динамической природы языка).

Мне показалось, что я достаточно хорошо разбираюсь в правилах области видимости Python, но эта проблема полностью остановила меня, и мое google-fu подвело меня (не то, чтобы я удивился - посмотрите на заголовок вопроса;)

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

Пример 1.

>>> x = 3
>>> class MyClass(object):
...     x = x
... 
>>> MyClass.x
3

Достаточно просто: во время определения класса мы можем получить доступ к переменным, определенным во внешней (в данном случае глобальной) области видимости.

Пример 2.

>>> def mymethod(self):
...     return self.x
... 
>>> x = 3
>>> class MyClass(object):
...     x = x
...     mymethod = mymethod
...
>>> MyClass().mymethod()
3

Опять же (игнорируя на данный момент почему кто-то может захотеть сделать это), здесь нет ничего неожиданного: мы можем получить доступ к функциям во внешней области.

Примечание: в качестве фредерикаг ниже, эта функция, кажется, не работает.Вместо этого см. Пример 5 (и далее).

Пример 3.

>>> def myfunc():
...     x = 3
...     class MyClass(object):
...         x = x
...     return MyClass
... 
>>> myfunc().x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in myfunc
  File "<stdin>", line 4, in MyClass
NameError: name 'x' is not defined

По сути, это то же самое, что и в примере 1: мы получаем доступ квнешняя область видимости из определения класса, только в этот раз эта область не является глобальной, благодаря myfunc().

Редактировать 5: Как указано @ user3022222ниже , я испортил этот пример в моей первоначальной публикации.Я считаю, что это терпит неудачу, потому что только функции (не другие блоки кода, как это определение класса) могут получить доступ к переменным в охватывающей области.Для блоков нефункционального кода доступны только локальные, глобальные и встроенные переменные.Более подробное объяснение доступно в этом вопросе

Еще одно:

Пример 4.

>>> def my_defining_func():
...     def mymethod(self):
...         return self.y
...     class MyClass(object):
...         mymethod = mymethod
...         y = 3
...     return MyClass
... 
>>> my_defining_func()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in my_defining_func
  File "<stdin>", line 5, in MyClass
NameError: name 'mymethod' is not defined

Гм... простите?

Чем это отличается от примера 2?

Я совершенно сбит с толку.Пожалуйста, разберитесь со мной.Спасибо!

PS из-за того, что это не просто проблема с моим пониманием, я пробовал это на Python 2.5.2 и Python 2.6.2.К сожалению, это все, к чему у меня есть доступ в данный момент, но они оба демонстрируют одинаковое поведение.

Редактировать Согласно http://docs.python.org/tutorial/classes.html#python-scopes-and-namespaces: в любой момент во время выполнения,не менее трех вложенных областей, пространства имен которых доступны напрямую:

  • самая внутренняя область, которая ищется первой, содержит локальные имена
  • области всех вмещающих функций, поиск которых начинается сближайшая охватывающая область, содержит нелокальные, но также и неглобальные имена
  • следующая за последней область содержит глобальные имена текущего модуля
  • самая внешняя область (последняя найденная) - этопространство имен, содержащее встроенные имена

# 4.кажется контрпримером ко второму из них.

Edit 2

Пример 5.

>>> def fun1():
...     x = 3
...     def fun2():
...         print x
...     return fun2
... 
>>> fun1()()
3

Редактировать 3

Как указал @ Frédéric, присваивание переменной с тем же именем, что и во внешней области видимости, «маскирует» внешнюю переменную, предотвращая назначение изфункционирует.

Так что эта модифицированная версия примера 4 работает:

def my_defining_func():
    def mymethod_outer(self):
        return self.y
    class MyClass(object):
        mymethod = mymethod_outer
        y = 3
    return MyClass

my_defining_func()

Однако это не так:

def my_defining_func():
    def mymethod(self):
        return self.y
    class MyClass(object):
        mymethod_temp = mymethod
        mymethod = mymethod_temp
        y = 3
    return MyClass

my_defining_func()

Я до сих пор не до конца понимаю, почему этопроисходит маскирование: не должно ли происходить связывание имени при назначении?

В этом примере по крайней мере содержится некоторая подсказка (и более полезное сообщение об ошибке):

>>> def my_defining_func():
...     x = 3
...     def my_inner_func():
...         x = x
...         return x
...     return my_inner_func
... 
>>> my_defining_func()()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in my_inner_func
UnboundLocalError: local variable 'x' referenced before assignment
>>> my_defining_func()
<function my_inner_func at 0xb755e6f4>

Таким образом, кажется, чтолокальная переменная определяется при создании функции (что успешно), в результате чего локальное имя «резервируется» и, таким образом, маскируется имя внешней области при вызове функции.

Интересно.

Спасибо Фредерику за ответ (ы)!

Для справки: документы по питону :

Важно понимать, что области действия определяются текстуально:Глобальная область действия функции, определенной в модуле, - это пространство имен этого модуля, независимо от того, откуда и каким псевдонимом вызывается функция.С другой стороны, фактический поиск имен выполняется динамически, во время выполнения - однако определение языка развивается в сторону статического разрешения имен, во время «компиляции», поэтому не полагайтесь на динамическое разрешение имен!(Фактически, локальные переменные уже определены статически.)

Редактировать 4

Реальный ответ

Это, казалось бы, запутанное поведение вызвано статически вложенными областями Python, как определено в PEP 227 .На самом деле это не имеет ничего общего с PEP 3104 .

Начиная с PEP 227:

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

[...]

Пример из Tim Peters демонстрирует потенциальные ловушки вложенных областей действия при отсутствии объявлений:

i = 6
def f(x):
    def g():
        print i
    # ...
    # skip to the next page
    # ...
    for i in x:  # ah, i *is* local to f, so this is what g sees
        pass
    g()

Вызов g () будет ссылаться на переменную i, связанную в f () циклом for.Если g () вызывается до выполнения цикла, возникает ошибка NameError.

Позволяет запустить две более простые версии примера Тима:

>>> i = 6
>>> def f(x):
...     def g():
...             print i
...     # ...
...     # later
...     # ...
...     i = x
...     g()
... 
>>> f(3)
3

, когда g() не делаетНе находя i во внутренней области видимости, он динамически ищет снаружи, находя область i в области f, которая была связана с 3 посредством присваивания i = x.

Но изменение порядка последних двух операторов в f вызывает ошибку:

>>> i = 6
>>> def f(x):
...     def g():
...             print i
...     # ...
...     # later
...     # ...
...     g()
...     i = x  # Note: I've swapped places
... 
>>> f(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in f
  File "<stdin>", line 3, in g
NameError: free variable 'i' referenced before assignment in enclosing scope

Помня, что в PEP 227 сказано: «Правила разрешения имен типичны для статически ограниченных языков», давайте посмотрим на) эквивалентное предложение версии C:

// nested.c
#include <stdio.h>

int i = 6;
void f(int x){
    int i;  // <--- implicit in the python code above
    void g(){
        printf("%d\n",i);
    }
    g();
    i = x;
    g();
}

int main(void){
    f(3);
}

скомпилируйте и запустите:

$ gcc nested.c -o nested
$ ./nested 
134520820
3

Так что, в то время как C с радостью будет использовать несвязанную переменную (используя все, что там было раньше: 134520820в этом случае) Python (к счастью) отказывается.

В качестве интересного примечания статически вложенные области видимости позволяют то, что Алекс Мартелли назвал"единственной наиболее важной оптимизацией, которую выполняет компилятор Pythones: локальные переменные функции не хранятся в dict, они находятся в узком векторе значений, и каждая локальная переменная доступа использует индекс в этом векторе, а не поиск имени. "

Ответы [ 2 ]

19 голосов
/ 28 ноября 2010

Это артефакт правил разрешения имен в Python: у вас есть доступ только к глобальной и локальной областям, но не к областям между ними, например. не для вашей непосредственной внешней сферы.

РЕДАКТИРОВАТЬ: Выше было плохо сформулировано, у вас действительно есть доступ к переменным, определенным во внешних областях, но с помощью x = x или mymethod = mymethod из неглобальной namespace, вы фактически маскируете внешнюю переменную той, которую определяете локально.

В примере 2 ваша непосредственная внешняя область является глобальной областью, поэтому MyClass может видеть mymethod, но в примере 4 ваша непосредственная внешняя область равна my_defining_func(), поэтому она не может, потому что внешнее определение mymethod уже замаскирован своим локальным определением.

См. PEP 3104 для получения дополнительной информации о разрешении нелокальных имен.

Также обратите внимание, что по причинам, изложенным выше, я не могу запустить пример 3 под Python 2.6.5 или 3.1.2:

>>> def myfunc():
...     x = 3
...     class MyClass(object):
...         x = x
...     return MyClass
... 
>>> myfunc().x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in myfunc
  File "<stdin>", line 4, in MyClass
NameError: name 'x' is not defined

Но сработает следующее:

>>> def myfunc():
...     x = 3
...     class MyClass(object):
...         y = x
...     return MyClass
... 
>>> myfunc().y
3
5 голосов
/ 27 ноября 2013

Этому посту несколько лет, но он один из редких, где обсуждается важная проблема области видимости и статического связывания в Python.Тем не менее, существует важное недоразумение автора для примера 3, которое может запутать читателей.(не принимайте как должное, что все остальные верны, просто я только подробно рассмотрел вопросы, поднятые в примере 3).Позвольте мне уточнить, что произошло.

В примере 3

def myfunc():
    x = 3
    class MyClass(object):
        x = x
    return MyClass

>>> myfunc().x

должен возвращать ошибку, в отличие от того, что сказал автор поста.Я считаю, что он пропустил ошибку, потому что в примере 1 x был назначен 3 в глобальной области видимости.Таким образом, неправильное понимание того, что произошло.

Объяснение подробно описано в этом посте Как ссылки на переменные разрешаются в Python

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...