Как Python уничтожает мусор? - PullRequest
0 голосов
/ 06 мая 2020

У меня вопрос о поведении этой простой функции. Вот код:

def foo():
    pi = 3.14
    def f():
        return pi
    return f
F = foo() 
F() # this is returning the 3.14

Почему функция f возвращает 3.14? Я думал, что после выполнения функции все локальное пространство имен должно быть уничтожено, не так ли? Итак, функция foo в конце возвращает указатель на объявленную функцию f (функция будет размещена в куче), но переменная pi должна быть уничтожена как переменная стека?

Ответы [ 2 ]

5 голосов
/ 06 мая 2020

Почему функция f возвращает [...] 3,14? Я думал, что после выполнения функции все локальное пространство имен должно быть уничтожено is'nt?

Да и нет.

В этом случае необходимые переменные из локального пространства имен сохраняются как так называемое «замыкание» для локально определенной функции. В этом случае переменная pi остается доступной для этой функции до тех пор, пока она не понадобится.

Давайте немного уточним:

def foo():
    pi = 3.14
    def f():
        return pi
    return f

Это внешняя функция.

В CLI мы можем немного поиграться.

>>> foo
<function foo at 0x000001B4E5282E18>
>>> dir(foo)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

А, есть что-то вроде __closure__, слово, которое я использовал раньше. Что это?

>>> foo.__closure__
>>>

А?

>>> foo.__closure__ is None
True

А

>>> f = foo() # get the inner function
>>> f
<function foo.<locals>.f at 0x000001B4E5C6C158>
>>> f()
3.14

Хорошо. Посмотрим, что у него внутри:

>>> f.__closure__
(<cell at 0x000001B4E5C34A08: float object at 0x000001B4E5231648>,)

Что это теперь?

>>> c = f.__closure__[0]
>>> c
<cell at 0x000001B4E5C34A08: float object at 0x000001B4E5231648>

Ячейка?

>>> dir(c)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']
>>> c.cell_contents
3.14

Ах. Итак, f.__closure__[0] - это ячейка, что-то вроде контейнера, для значения, взятого из локального пространства имен выше.

В качестве бонуса мы могли бы изучить разборку функций:

>>> import dis
>>> dis.dis(foo)
  2           0 LOAD_CONST               1 (3.14)
              2 STORE_DEREF              0 (pi)

  3           4 LOAD_CLOSURE             0 (pi)
              6 BUILD_TUPLE              1
              8 LOAD_CONST               2 (<code object f at 0x000001B4E5C418A0, file "<stdin>", line 3>)
             10 LOAD_CONST               3 ('foo.<locals>.f')
             12 MAKE_FUNCTION            8
             14 STORE_FAST               0 (f)

  5          16 LOAD_FAST                0 (f)
             18 RETURN_VALUE
>>> dis.dis(f)
  4           0 LOAD_DEREF               0 (pi)
              2 RETURN_VALUE

Здесь мы видим, как строится f:

      3           4 LOAD_CLOSURE             0 (pi)

Загрузить переменную pi как закрытие (ячейку)

                  6 BUILD_TUPLE              1

Строить кортеж только с этой ячейкой

                  8 LOAD_CONST               2 (<code object f at 0x000001B4E5C418A0, file "<stdin>", line 3>)
                 10 LOAD_CONST               3 ('foo.<locals>.f')
                 12 MAKE_FUNCTION            8

Создать функцию с заданным именем, кодом и закрытием

                 14 STORE_FAST               0 (f)

Сохранить.

В функции доступ к закрывающим элементам осуществляется с помощью LOAD_DEREF.


Если мы немного расширим функцию, например,

def foo():
    pi = 3.14
    two = 2
    three = 3
    def f():
        return pi - three
    return f

, мы увидим, как обрабатываются и обрабатываются эти переменные:

>>> dis.dis(foo)
  2           0 LOAD_CONST               1 (3.14)
              2 STORE_DEREF              0 (pi)

  3           4 LOAD_CONST               2 (2)
              6 STORE_FAST               0 (two)

  4           8 LOAD_CONST               3 (3)
             10 STORE_DEREF              1 (three)

  5          12 LOAD_CLOSURE             0 (pi)
             14 LOAD_CLOSURE             1 (three)
             16 BUILD_TUPLE              2
             18 LOAD_CONST               4 (<code object f at 0x000001B4E5CA36F0, file "<stdin>", line 5>)
             20 LOAD_CONST               5 ('foo.<locals>.f')
             22 MAKE_FUNCTION            8
             24 STORE_FAST               1 (f)

  7          26 LOAD_FAST                1 (f)
             28 RETURN_VALUE

См. чем переменные pi и three отличаются от two: two сохраняется с STORE_FAST, остальные используют STORE_DEREF, чтобы их можно было передать функции.

>>> foo().__closure__
(<cell at 0x000001B4E5BC41F8: float object at 0x000001B4E5231528>, <cell at 0x000001B4E5C348B8: int object at 0x0000000050816120>)

Теперь у него два элемента:

>>> foo().__closure__[0].cell_contents
3.14
>>> foo().__closure__[1].cell_contents
3

И вот как он используется:

>>> dis.dis(f)
  6           0 LOAD_DEREF               0 (pi)
              2 LOAD_DEREF               1 (three)
              4 BINARY_SUBTRACT
              6 RETURN_VALUE

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

import time
import threading

def foo():
    c = 0
    def run():
        nonlocal c
        while c < 50:
            c += 1
            time.sleep(1.0)
    t = threading.Thread(target=run)
    t.start()
    def f(): return c
    return f

Здесь поток увеличивает значение переменной каждую секунду. Если мы теперь выполним f = foo(), мы получим эту внутреннюю функцию, которая возвращает разные значения, если вызывается несколько раз с некоторым временем между вызовами.

0 голосов
/ 06 мая 2020

этот код возвращает число, а именно 3.14:

def foo():
    pi = 3.14
    def f():
        return pi
    return f()
...