Область видимости и замыкания в Python

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

Правило LEGB
  • Local локальная область функции
  • Enclosing область внешней функции
  • Global глобальное пространство
  • Built-in встроенном (модуль builtis)
Если не указаны инструкции, то поиск переменных по пространствам имен происходит с низу верх
local->enclosing->global->built-in
x = 10
print('Global x =', x)


def fun_enclosing():
    print('Enclosing x =', x)

    def fun_local():
        print('Local x =', x)

    fun_local()

fun_enclosing()

Global x = 10
Enclosing x = 10
Local x = 10

Любое присвоение переменной в функции создает новую локальную переменную
x = 10
print('Global x =', x)


def fun_enclosing():
    x = 20  # создания новой переменной в пространстве имен fun_enclosing
    print('Enclosing x =', x)

    def fun_local():
        print('Local x =', x)

    fun_local()

fun_enclosing()

Global x = 10
Enclosing x = 20
Local x = 20

Если необходимо изменить глобальную переменную необходимо сначала указать ее с инструкцией global
x = 10
print('Global x =', x)


def fun_enclosing():
    global x
    x = 20
    print('Enclosing x =', x)

    def fun_local():
        print('Local x =', x)

    fun_local()

fun_enclosing()

print('Global after x =', x)

Global x = 10
Enclosing x = 20
Local x = 20
Global after x = 20

Переменные global сразу будут искать в глобальном пространстве, миную локальное и внешнее.
x = 10
print('Global x =', x)


def fun_enclosing():
    x = 20
    print('Enclosing x =', x)

    def fun_local():
        global x
        print('Local x =', x)

    fun_local()

fun_enclosing()

print('Global after x =', x)

Global x = 10
Enclosing x = 20
Local x = 10
Global after x = 10

Если необходимо изменить переменную из внешней функции, то нужна инструкция nonlocal
x = 10
print('Global x =', x)


def fun_enclosing():
    x = 20
    print('Enclosing x =', x)

    def fun_local():
        nonlocal x
        x = 30
        print('Local x =', x)

    fun_local()

    print('Enclosing after x =', x)

fun_enclosing()

print('Global after x =', x)

Global x = 10
Enclosing x = 20
Local x = 30
Enclosing after x = 30
Global after x = 10

Если переменной было присвоено значение внутри функции, то обратится к глобальным уже не получится
x = 10

def fun_local():
    print(x)
    x = 5

fun_local()

UnboundLocalError: local variable 'x' referenced before assignment


Замыкания

Это функция у которой есть ссылки на переменные из внешней функции. Такие переменные называются свободными(freevars)
def make_multiplier_of(n):
    """принимает n и создает функцию(multiplier) которая будет умножать n на аргумент(x) из созданной функции"""

    def multiplier(x):
        """у объекта multiplier будет ссылка на переменную n,
           к которой она сможет обращаться уже после создания"""
        return x * n

    return multiplier

times3 = make_multiplier_of(3)
times5 = make_multiplier_of(5)

print(times3(9))
print(times5(3))
print(times5(times3(2)))

# ----
print('все переменные: ', times3.__code__.co_varnames)
print('свободные из внешней функции: ', times3.__code__.co_freevars)
for indx, fv_name in enumerate(times3.__code__.co_freevars):
    print('{0} = {1}'.format(fv_name, times3.__closure__[indx].cell_contents))

print('все переменные: ', times5.__code__.co_varnames)
print('свободные из внешней функции: ', times5.__code__.co_freevars)
for indx, fv_name in enumerate(times5.__code__.co_freevars):
    print('{0} = {1}'.format(fv_name, times5.__closure__[indx].cell_contents))

27
15
30
все переменные:  ('x',)
свободные из внешней функции:  ('n',)
n = 3
все переменные:  ('x',)
свободные из внешней функции:  ('n',)
n = 5

Если необходимо изменять переменную из внешней функции, то нужно использовать инструкцию nonlocal
def make_averanger():
    count = 0
    total = 0

    def averanger(new_value):
        nonlocal count, total
        count += 1
        total += new_value
        return total / count
    return averanger

avg = make_averanger()
print(avg(10))
print(avg(20))
print(avg(100))

Но иногда такое поведения бывает не желательно
def makeActions():
    acts = []
    for i in range(5):
        acts.append(lambda x: i ** x)
    return acts

for act in makeActions():
    print(act(1), end=', ')

4, 4, 4, 4, 4
Обращения к i и соответственно получения его значения происходит во время вызова функции, в этот момент i уже имеет значения 4.

Это можно исправить присвоив значению по умолчанию, так как оно вычисляется в момент создание вложенной функции
def makeActions():
    acts = []
    for i in range(5):
        acts.append(lambda x, i=i: i ** x)
    return acts

for act in makeActions():
    print(act(1), end=', ')


print()


def makeActions():
    acts = []
    for i in range(5):
        def func(x, i=i):
            return x * i
        acts.append(func)
    return acts

for act in makeActions():
    print(act(1), end=', ')

0, 1, 2, 3, 4
0, 1, 2, 3, 4

А так как значения по умолчанию присваивается во время создании функции, то это значения будет общим для всех вызовов
def func(v, x=[]):
    x.append(v)
    print(x)

for i in range(5):
    func(i)

[0]
[0, 1]
[0, 1, 2]
[0, 1, 2, 3]
[0, 1, 2, 3, 4]



Комментарии

Популярные сообщения из этого блога

Асинхронное выполнение процедур или триггера в MS SQL Server

Рекурсивные SQL запросы

Кратко про SQLAlchemy Core