В чем разница между «привязкой к переменным» и «привязкой к объекту» в Python - PullRequest
3 голосов
/ 09 декабря 2010

Когда я изучал «именование и связывание» в Python, я увидел следующий пример:

>>> def testClosure(maxIndex):
        def closureTest(maxIndex=maxIndex):
            return maxIndex
        maxIndex += 5
        return closureTest()
>>> print(testClosure(10))
10


>>> def testClosure(maxIndex):
        def closureTest():
            return maxIndex
       maxIndex += 5
       return closureTest()
>>> print(testClosure(10))
15

Автор объяснил это так: В последней функции свободная переменная во внутренней области видимости связывается с переменной во внешней области видимости, а не с объектами.

Тогда мой вопрос: в чем разница между «связать с переменной» и «связать с объектом» в Python?

Кроме того, это очень сложно: результат будет другим, если я переставлю код.

>>> def testClosure(maxIndex):
        maxIndex += 5
        def closureTest(maxIndex=maxIndex):
            return maxIndex
        return closureTest()

>>> print(testClosure(10))
15

Заранее спасибо.

Ответы [ 3 ]

8 голосов
/ 09 декабря 2010

Два ключевых факта:

  1. Python использует правило LEGB для поиска значения (голых) имен переменных.LEGB расшифровывается как Local, Extended, Global, Builtins.Это означает, что имя переменной «привязывается» к локальному значению, и если его нет, то значение ищется в расширенной области, и, если такой переменной нет, поиск выполняется в глобальной области и, наконец, ввстроенная область видимости.
  2. При определении такой функции, как

    def closureTest(maxIndex=maxIndex):
        return maxIndex
    

    значения по умолчанию фиксированы на definition-time, а не run-time.Под definition-time я подразумеваю время обработки оператора def - когда функция определена.Под run-time я подразумеваю время, когда функция вызывается.Обратите внимание, что когда у вас есть вложенные функции, внутренняя функция definition-time появляется после вызова внешней функции.


Первый пример усложняется тем фактом, что имя переменной maxIndex используется слишком часто.Вы поймете первый пример, если впервые поймете это:

>>> def testClosure(maxIndex):              
        def closureTest(index=maxIndex):     # (1)
            return index                     
        maxIndex += 5
        return closureTest()                 # (2)
>>> print(testClosure(10))
  • (1) Во время определения значение индекса по умолчанию устанавливается равным 10.
  • (2)Когда closureTest() вызывается без аргументов, index устанавливается в значение по умолчанию 10. Так что это возвращаемое значение.

def testClosure(maxIndex):
    def closureTest():
        return maxIndex                 # (3)
   maxIndex += 5
   return closureTest()                 # (4)
print(testClosure(10))
  • (3) Правило LEGB указывает Python искать значение maxIndex в локальной области видимости.В локальной области не определено maxIndex, поэтому она выглядит в расширенной области.Он находит maxIndex, который является аргументом для testClosure.

  • (4) К моменту вызова closureTest(), maxIndex имеет значение 15. Так что maxIndex возвращено closureTest() равно 15.


>>> def testClosure(maxIndex):
        maxIndex += 5                           # (5)    
        def closureTest(maxIndex=maxIndex):     # (6)
            return maxIndex
        return closureTest()                    # (7)
  • (5) maxIndex равно 15

  • (6) Для closureTest maxIndex установлено значение по умолчанию 15 во время определения.

  • (7) Когда вызывается closureTest() без аргументов, по умолчаниюзначение для maxIndex используется.Возвращается значение 15.

3 голосов
/ 09 декабря 2010

Это может быть менее запутанным, если вы подумаете о привязке, которая происходит в выражении параметра оператора 'def'.Когда вы видите 'def closureTest (maxIndex = maxIndex):', это оператор типа 'if' или 'while', за которым следует набор кода, который нужно проанализировать и привязать к функции (вызываемый объект).

Оператор 'def' оценивается в той области, где он найден (концептуально на том же уровне вложенности / отступа).Его выражение параметра объявляет, как аргументы будут сопоставлены с именами в пределах собственной области функции.Любые из тех, которые предоставляют значение по умолчанию (например, maxIndex в ваших примерах), создают объект функции с соответствующим именем параметра, привязанным к какому-либо объекту, который был назван или создан в то время (в рамках действия) оператора 'def'.

Когда функция вызывается, каждый из ее параметров (имен в пределах ее области действия) связывается с любыми аргументами, предоставленными функции.Любые необязательные параметры, таким образом, оставляются привязанными к тому, какие аргументы были оценены как часть оператора def.

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

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

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

Если вы определяете функциюсо списком параметров, таким как '(myList = list ())', тогда список будет создан при выполнении определения.Он будет доступен из вызовов кода функции каждый раз, когда функция вызывается без аргументов.Любой вызов с аргументом будет выполнен с этим именем параметра, связанным с аргументом, предоставленным при вызове.(На объект, созданный во время def, все еще ссылается объект кода, который был определен - набор с отступом после оператора def).

Ничего из этого не будет иметь никакого смысла, если вы не сохраните различие междупараметры и аргументы.Помните, что параметры являются частью определения функции;они определяют, как аргументы будут отображаться в локальное пространство имен функции.Аргументы являются частью вызова;это те вещи, которые передаются при любом вызове функции.

Надеюсь, это поможет.Я понимаю, что это различие тонкое и термины очень часто используются неправильно, как если бы они были взаимозаменяемыми (в том числе в документации Python).

0 голосов
/ 09 декабря 2010
>>> def testClosure(maxIndex):
        def closureTest(maxIndex=maxIndex): # You're creating a function with a kwarg of maxIndex
            return maxIndex                 # which references the int passed in from outside
        maxIndex += 5                       # Incrementing maxIndex means that the maxIndex in the outer
        return closureTest()                # scope now points to a different int.
>>> print(testClosure(10))
10

Рассмотрим:

>>> a = b = 1
>>> a += 1
>>> a
2
>>> b
1

Я не знаю, что вы подразумеваете под «привязать к объекту» и «привязать к переменной». «Переменные» - это ссылки на вещи, когда вы увеличиваете a, вы изменяете его, чтобы ссылаться на другое значение, b все еще ссылается на исходное значение.

Когда вы не передаете значение maxIndex во внутреннюю функцию, а затем запрашиваете его, так как оно не определено в локальной области видимости, оно ищется в расширенной области.

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

>>> import timeit
>>> def testClosure(maxIndex):
...     def closureTest(maxIndex=maxIndex):
...         return maxIndex
...     maxIndex += 5
...     return closureTest()
...
>>> def testClosure2(maxIndex):
...     def closureTest():
...         return maxIndex
...     maxIndex += 5
...     return closureTest()
...
>>> timeit.Timer('testClosure(10)','from __main__ import testClosure').timeit()
1.4626929759979248
>>> timeit.Timer('testClosure2(10)','from __main__ import testClosure2').timeit()
1.7869210243225098
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...