Создание класса внутри функции и доступ к функции, определенной в области содержащихся функций - программирование
Подтвердить что ты не робот

Создание класса внутри функции и доступ к функции, определенной в области содержащихся функций

Edit

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

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

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

Я думал, что у меня неплохая ручка в правилах определения 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?

Я полностью одурачен. Пожалуйста, разобраться со мной. Спасибо!

P.S. на случай, что это не просто проблема с моим пониманием, я пробовал это на Python 2.5.2 и Python 2.6.2. К сожалению, это все, к чему у меня есть доступ в настоящий момент, но они оба демонстрируют одинаковое поведение.

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

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

# 4. представляется противоположным примером для второго из них.

Изменить 2

Пример 5.

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

Изменить 3

Как заметил Фредерик, назначение переменной с тем же именем, что и во внешней области видимости, "маскирует" внешнюю переменную, предотвращая ее выполнение.

Итак, эта модифицированная версия примера 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>

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

Интересно.

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

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

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

Изменить 4

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

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

От PEP 227:

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

[...]

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

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(), для петля. Если 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 (к счастью) отказывается.

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

4b9b3361

Ответ 1

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

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

В примере 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

Ответ 2

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

В примере 3

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

>>> myfunc().x

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

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