Функции подробнее

Описано на основе

  • Лутц, Изучаем Python. Глава 16. Основы фукций; Глава 17

Преимущества функциий vs copy-paste

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

Принципы работы функций в питоне

  • def - исполняемый программный код
    • Функция не существует, пока до нее не дошел интерпретатор и не выполнил ее.
    • Можно вставлять def внутрь циклов, if или в другие инструкции def.
  • def создает объект и присваивает ему имя
    • как в операции =, имя - ссылка на объект-фукнцию.
    • можно сделать несколько ссылок, сохранить в списке и тп.
    • к функции можно прикрепить определяемые пользователем атрибуты.
  • выражение lambda создает объект и возвращает его в виде результата
  • return передает объект результата вызывающей программе
    • вызывающий код приостанавливает свою работу, запоминается откуда вызывается функция;
    • управление передается в функцию;
    • выполняется функция и быть может возвращает значение (если есть return);
    • значение вызванной функции равно возвращаемому значению при этом вызове.
  • yield передает объект результата вызывающей программе и запоминает, где был произведен возврат
  • аргументы передаются присваиванием и по ссылке
    • переменной-аргументу присваивается ссылка на передаваемый объект.
  • global объявляет переменные, глобальные для модуля, без присваивания им значений
    • по умолчанию все имена, которым присваиваются значения, являются локальными для этой функции и существуют только во время выполнения фукнции.
    • global myvar - внутри функции делает переменную myvar видимой в текущем модуле.
  • nonlocal объявляет переменные, находящиеся в области видимости объемлющей функции, без присваивания им значений (Python 3)
    • храним в них информацию о состоянии - информация восстанавливается в момент вызова функции, можно обойтись без global
  • аргументы, возвращаемые значения и переменные НЕ объявляются

Далее рассмотрим эти концепции с примерами кода.

def исполняют во время выполнения

Нет понятия компиляции кода. Можно написать так:

if test:
    def func(): # Определяет функцию таким способом
...
else:
    def func(): # Или таким способом
...
...
func()          # Вызов выбранной версии

def похож на оператор присваивания (=).

  • def НЕ интерпретируются, пока они не будут достигнуты;
  • код внутри функции НЕ выполняется, пока функция не вызвана.

Свяжем код функции с другим именем:

myfunc = func  # связывание объекта-функции с именем myfunc
myfunc()       # вызов функции (не важно по какому имени)

Атрибуты функции

К функции можно присоединить атрибут. В них можно сохранять информацию.

def func(): ...   # Создает объект функции
func()            # Вызывает объект
func.attr = value # Присоединяет атрибут attr к объекту и записывает в него значение value

Ничего не возвращает

Не обязательно в фукнции писать return (или yield). Функция может ничего не возвращать.

Результат вызова такой функции None

>>> def hi(name):
...     print('Hello,', name)
...    
>>> x = hi('Mike')
Hello, Mike
>>> x
None

Полиморфизм

Создание функцию:

>>> def times(x, y):  # Создать функцию и связать ее с именем
...     return x * y  #Тело, выполняемое при вызове функции

Вызов функции:

>>> times(2, 4)
8
>>> times('Hi', 3)
HiHiHi

Одна и та же функция times используется для разного: умножения чисел и повторения последовательностей.

Полиморфизм означает, что смысл операций зависит от типов операндов.

Динамическая типизация и полиморфизм - основа языка питон. Не нужно в функциях писать ограничения на типы. Либо операция поддерживает тип операнда, либо автоматически возбуждается исключение. Не ломайте этот механизм.

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

Функция ищет пересечение двух последовательностей:

def intersect(seq1, seq2):
    res = []
    for x in seq1:
        if x in seq2:
            res.append(x)
    return res

Заметим, что в функции первый аргумент должен быть итерируемым (с ним дожен работать for), а воторой аргумент - поддерживать оператор in. Других ограничений на объекты нет.

Этот же код (как будет рассказано дальше), можно записать в виде генератора списков:

[x for x in seq1 if x in seq2]

Локальные переменные

Локальная переменная - имя, которое доступно только внутри инструкции def и существует только во время выполнения функции (после окончания функции эти переменные уничтожаются).

def intersect(seq1, seq2):
    res = []
    for x in seq1:
        if x in seq2:
            res.append(x)
    return res

Здесь локальные переменные:

  • res - потому что ей присваивают;
  • аргументы передаются через операцию присваивания, поэтому seq1 и seq2 - локальные переменные;
  • цикл for присваивает значения переменной x, поэтому она тоже локальная переменная.

Они появляются при вызове фукнции и исчезают после ее окончания.

Область видимости (пространство имен, namespace)

Область видимости (пространство имен) - область, где хранятся переменные. Здесь определяются переменные и делают поиск имен.

Операция = связывает имена с областью видимости (пространством имен)

Каждая функция добавляет свое пространство имен:

  • имена внутри def видны только внутри def.
  • можно делать одинаковые имена (x) в разных простанствах имен (другом def или во всем модуле).

Области видимости:

  • локальная переменная - присвоение внутри def; видна только внутри def.
  • нелокальная переменная - в пределах объемлющей инструкции def; там и видна.
  • глобальная переменная - вне всех def; глобальная для всего файла.

Лексическая область видимости - потому что определяется тем, где первый раз было =.

Модули и функции

  • Модуль - глобальная область видимости
    • пространство имен, где создаются переменные на верхнем уровне в файле модуля.
    • глобальные переменные с точки зрения:
      • внешнего мира - это атрибуты модуля;
      • внутри модуля - это обычные переменные.
  • Глобальная область видимости - это ОДИН файл.
    • слышим "глобальный" подразумеваем "один модуль", более глобального в питоне нет.
  • Каждый вызов функции создает новую локальную область видимости
    • рекурсия - основывается на множественности (и разных!) областях видимости.
  • Операция присваивания создает локальные имена, если они не были объ- явлены глобальными или нелокальными
    • иначе используем global, nonlocal
  • Все остальные имена являются локальными в области видимости объ- емлющей функции, глобальными или встроенными.
    • Не было присвоения? Ищи в каком пространстве имен эта переменная!
      • объемлющей локальной области видимости (внутри объемлющей инструкции def);
      • глобальной (в пространстве имен модуля);
      • встроенной (предопределенные имена в модуле buildins)

Присвоил? Определил область локальности.

Операции непосредственого изменения объекта - это НЕ присвоение! Они не делают переменную локальной.

x.append(7)      # остается глобальной
y = [1, 2, 3]    # y стала локальной в текущем пространстве

Разрешение имен: правило LEGB

Для инструкции def:

  • Поиск имен ведется самое большее в четырех областях видимости: локальной, затем в объемлющей функции (если таковая имеется), затем в глобальной и, наконец, во встроенной.
  • По умолчанию операция присваивания создает локальные имена.
  • Объявления global и nonlocal отображают имена на область видимости вмещающего модуля и функции соответственно.

Правило LEGB:

  • Когда внутри функции выполняется обращение к неизвестному имени, интерпретатор пытается отыскать его в четырех областях видимости – в локальной (local, L), затем в локальной области любой объемлющей инструк- ции def (enclosing, E) или в выражении lambda, затем в глобальной (global, G) и, наконец, во встроенной (built-in, B).

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

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

Это правила поиска имен переменных. Для атрибутов объектов применяются другие правила (см. Наследование).

# Глобальная область видимости
X = 99          # X и func определены в модуле: глобальная область
def func(Y):    # Y и Z определены в функции: локальная область
    # Локальная область видимости
    Z = X + Y   # X – глобальная переменная
    return Z

func(1)         # func в модуле: вернет число 100
  • Глобальные имена: X, func (так как объявлены на верхнем уровне модуля)
  • Локальные имена: Y (аргументы передаются через присвоение), Z (создается через =)
x = 88          # глобальная переменная x

def func():
    x = 1       # создали локальную переменную x, переопределяет глобальную

func()
print(x)        # 88, печатаем глобальную переменную

Встроенная область видимости (biuldins)

В действительности, встроенная область видмости – это всего лишь встроенный модуль с именем builtis, но для того, чтобы использовать имя builtis, необходимо импортировать модуль builtis, потому что это имя само по себе не является встроенным.

>>> import builtins
>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis',
...множество других имен опущено...
'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set',
'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple',
'type', 'vars', 'zip']

Использование встроенных функций:

>>> zip             # Обычный способ
<class zip>
>>> import builtins # Более сложный способ
>>> builtins.zip
<class zip>

НЕ переопределяйте встроенные имена!

def hider():
    open = 'spam'     # Локальная переменная, переопределяет встроенное имя
    ...
    open('data.txt')  # В этой области видимости файл не будет открыт!

global и nonlocal

global и nonlocal НЕ объявляет переменную, а определяет пространство имен переменной. Позволяет изменять переменные за пределами текущей def.

Глобальные имена – это имена, которые определены на верхнем уровне вмещающего модуля.

  • читать такое имя можно и без global;
  • global нужно, чтобы присваивать глобальным именам.

Нелокальные имена - внешние для текущей def, но не верхний уровень модуля.

X = 88          # Глобальная переменная X
def func():
    global X
    X = 99      # Глобальная переменная X: за пределами инструкции def
func()
print(X)        # Выведет 99

Здесь все переменные тоже глобальные:

y, z = 1, 2         # Глобальные переменные в модуле
def all_global():
    global x        # Объявляется глобальной для присваивания
    x = y + z       # Объявлять y, z не требуется: применяется правило LEGB

Если x не существовала в момент вызова функции, то операция = создаст переменную х в области видимости модуля

Меньше глобальных переменных

Почему нужно стремиться делать меньше глобальных переменных?

Пусть мы хотим модифицировать этот модуль или использовать в другой программе. Чему равно х? Это зависит от того, какие функции вызывались. Сложнее понять код, ибо нужно знать как выполняется ВСЯ программа (кого вызывали последней - func1 или func2, или их вообще не вызывали).

X = 99

def func1():
    global X
    X = 88

def func2():
    global X    
    X = 77

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

Не изменяйте глобальные переменные непосредственно в других модулях

Пусть сначала написали модуль first.py, потом написали модуль second.py и далее пытаемся понять, как работает программа. Изменение переменных модуля в другом модуле напрямую ухудщает читаемость кода.

# first.py
X = 99          # в first.py не знаем о second.py
# second.py
import first
print(first.X)  # читать - хорошо
first.X = 88    # изменять - плохо

Программист, поддерживающий first.py пытается найти кто его импортировал (и в какой директории) и изменил.

Меньше связей между модулями - проще поддерживать и изменять модули.

Лучше делать связи через вызовы функций и возвращаемые значения (проще контролировать).

# first.py
X = 99
def setX(new):      # интерфейс модуля
    global X
    X = new

# second.py
import first
first.setX(88)

Вложенные области видимости

Чтение переменной внутри функции. Ищем имя:

  • в локальной области видимости функции;
  • в локальных областях видимости объемлющих функций изнутри наружу;
  • в глобальной области видимости модуля;
  • в builtins (встроенная область видимости).

x = value внутри функции:

  • создает или изменяет имя х в текущей локальной области видимости функции;
  • если был unlocal x, то = создает или изменяет имя в ближайшей области видимости объемлющей функции.
  • если был global x, то = создает или изменяет имя в области видимости объемлющего модуля.
X = 99              # Имя в глобальной области видимости: не используется
def f1():
    X = 88          # Локальное имя в объемлющей функции
    def f2():
        print(X)    # Обращение к переменной во вложенной функции
    f2()
f1()                # Выведет 88: локальная переменная в объемлющей функции

Ищем в объемлющих областях, даже если объемлющая функция фактически уже вернула управление:

def f1():
    X = 88
    def f2():
        print(X)    # Сохраняет значение X в объемлющей области видимости
    return f2       # Возвращает f2, но не вызывает ее

action = f1()       # Создает и возвращает функцию
action()            # Вызов этой функции: выведет 88

Функция f2 помнит переменную Х в области видимости объемлющей функции f1, которая уже неактивна.

Замыкание (фабричная функция)

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

(Для сохранения состояний лучше использовать классы).

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

Определим внешнюю фукнцию, которая создает и возвращает внутреннюю функцию, не вызывая ее.

>>> def maker(N):
...     def action(X):      # Создать и вернуть функцию
...         return X ** N   # Функция action запоминает значение N в объемлющей
...     return action       # области видимости
...

Вызовем внешнюю функцию. Она возвращает ссылку на созданную ею вложенную функцию, созданную при выполнении вложенной инструкции def:

>>> f = maker(2)            # Запишет 2 в N
>>> f
<function action at 0x014720B0>

Вызовем по этой ссылке функцию (внутреннюю!):

>>> f(3)                    # Запишет 3 в X, в N по-прежнему хранится число 2
9
>>> f(4)                    # 4 ** 2
16

Вложенная функция продолжает хранить число 2, значение переменной N в функции maker даже при том, что к моменту вызова функции action функция maker уже завершила свою работу и вернула управление. В действительности имя N из объемлющей локальной области видимости сохраняется как информация о состоянии, присоединенная к функции action, и мы получаем обратно значение аргумента, возведенное в квадрат. Теперь, если снова вызвать внешнюю функцию, мы получим новую вложенную функцию уже с другой информацией о состоянии, присоединенной к ней, - в результате вместо квадрата будет вычисляться куб аргумента, но ранее со храненная функция по-прежнему будет возвращать квадрат аргумента:

>>> g = maker(3)    # Функция g хранит число 3, а f – число 2
>>> g(3)            # 3 ** 3
27
>>> f(3)            # 3 ** 2
9

при каждом обращении к фабричной функции, как в данном примере, произведенные ею функции сохраняют свой собственный блок данных с информацией о состоянии. В нашем случае благодаря тому, что каждая из функций получает свой собственный блок данных с информацией о состоянии, функция, которая присваивается имени g, запо минает число 3 в переменной N функции maker, а функция f – число 2.

Где используется?

  • lambda
  • декораторы

Лучше для хранения информации подходят классы или глобальные переменные.

Избавьтесь от вложенности

Плоское лучше вложенного - один из принципов питона.

def f1():
    X = 88
    def f2():
        print(X)    # Сохраняет значение X в объемлющей области видимости
    return f2       # Возвращает f2, но не вызывает ее

action = f1()       # Создает и возвращает функцию
action()            # Вызов этой функции: выведет 88

Напишите лучше без вложенных функций (делает то же самое):

>>> def f1():
...     x = 88  # Передача значения x вместо вложения функций
...     f2(x)   # Опережающие ссылки считаются допустимыми
...
>>> def f2(x):
...     print(x)
...
>>> f1()
88

Вложенные области видимости и lambda-выражения

lambda (как и def) порождает новую область видимости.

def func():
    x = 4
    action = (lambda n: x ** n) # запоминается x из объемлющей инструкции def
    return action

x = func()
print(x(2))                     # Выведет 16, 4 ** 2

То же самое, с ручной передачаей значения (работет и в старых версиях питона):

def func():
    x = 4
    action = (lambda n, x=x: x ** n) # Передача x вручную
    return action

Когда нужно передать значение в lambda вручную? Если мы используем lambda в цикле. Хотим, чтобы каждая lambda запомнила свое значение i. Получилось, что все lambda запомнили последнее значение i (4).

>>> def makeActions():
...     acts = []
...     for i in range(5): # Сохранить каждое значение i
...         acts.append(lambda x: i ** x) # Все запомнят последнее значение i!
...     return acts
...
>>> acts = makeActions()
>>> acts[0]
<function <lambda> at 0x012B16B0>
>>> acts[0](2) # Все возвращают 4 ** 2, последнее значение i
16
>>> acts[2](2) # Здесь должно быть 2 ** 2
16
>>> acts[4](2) # Здесь должно быть 4  ** 2
16

Поиск переменной в объемлющей области видимости производится позднее, при вызове вложенных функций, в результате все они получат одно и то же значение (значение, которое имела переменная цикла на последней итерации). То есть каждая функция в списке будет возвращать 4 во второй степени, потому что во всех них переменная i имеет одно и то же значение.

Надо явно сохранять значение из объемлющей области видимости в виде аргумента со значением по умолчанию вместо использования ссылки на переменную из объемлющей области видимости. Значения по умолчанию вычисляются в момент создания вложенной функции (а не когда она вызывается), поэтому каждая из них сохранит свое собственное значение i:

>>> def makeActions():
...     acts = []
...     for i in range(5): # Использовать значения по умолчанию
...         acts.append(lambda x, i=i: i ** x) # Сохранить текущее значение i
...     return acts
...
>>> acts = makeActions()
>>> acts[0](2) # 0 ** 2
0
>>> acts[2](2) # 2 ** 2
4
>>> acts[4](2) # 4 ** 2
16

На подобный эффект можно натолкнуться в коде, которые генерируют функции-обработчики событий для GUI.

nonlocal - "пропустить локальную область видимости при поиске имен"

Для чтения значений переменных не нужно. Только для =.

  • global
    • вынуждает интерпретатор начинать поиск имен с области объемлю- щего модуля;
    • позволяет присваивать переменным новые значения.
    • Область поиска простирается вплоть до встроенной области видимости, если искомое имя не будет найдено в модуле,
    • при этом операция присваивания значений глобальным именам всегда будет создавать или изменять переменные в области видимости модуля.
  • nonlocal
    • ограничивает область поиска областями видимости объемлющих функций;
    • требует, чтобы перечисленные в инструкции имена уже существовали,
    • позволяет присваивать им новые значения;
    • в область поиска не входят глобальная и встроенная области видимости.
>>> def tester(start):
...     state = start           # Обращение к нелокальным переменным действует как обычно
...     def nested(label):
...         print(label, state) # Извлекает значение state из области видимости объемлющей функции
...         state += 1          # создается ЛОКАЛЬНАЯ переменная - хотели изменить нелокальную
...     return nested
...
>>> F = tester(0)
>>> F('spam')
spam 0
>>> F('ham')
ham 0

По умолчанию нельзя изменять значения переменной объемлющей области видимости

unlocal - позволяет изменять, даже если функция tester завершила работу к моменту вызова функции nested через переменню F:

>>> def tester(start):
...     state = start               # В каждом вызове сохраняется свое значение state
...     def nested(label):
...         nonlocal state          # Объект state находится
...         print(label, state)     # в объемлющей области видимости
...         state += 1              # Изменит значение переменной, объявленной как nonlocal
...     return nested
...
>>> F = tester(0)
>>> F('spam') # Будет увеличивать значение state при каждом вызове
spam 0
>>> F('ham')
ham 1
>>> F('eggs')
eggs 2

Напоминаем, каждый вызов фабричной фукнции tester будет создавать отдельную копию переменной state. Объект state, находящийся в объемлющей области видимости, фактически прикрепляется к возвращаемому объекту функции nested - каждый вызов функции tester создает новый, независимый объект state, благодаря чему изменение state в одной функции не будет оказывать влияния на другие. Вызываем еще эти же функции:

>>> G = tester(42)  # Создаст новую функцию, которая начнет счет с 42
>>> G('spam')
spam 42
>>> G('eggs')       # Обновит значение state до 43
eggs 43
>>> F('bacon')      # Но в функции F значение state останется прежним
bacon 3             # Каждая новая функция получает свой экземпляр state

unlocal переменная должна уже существовать (global - не обязательно, создаем новую)

>>> def tester(start):
...     def nested(label):
...         nonlocal state  # Нелокальные переменные должны существовать!
...         state = 0
...         print(label, state)
...     return nested
...
SyntaxError: no binding for nonlocal 'state' found
>>> def tester(start):
...     def nested(label):
...         global state    # Глобальные переменные могут отсутствовать
...         state = 0       # Создаст переменную в области видимости модуля
...         print(label, state)
...     return nested
...
>>> F = tester(0)
>>> F(‘abc’)
abc 0
>>> state
0

unlocal область видимости - без глобальной области модуля или built-in

>>> spam = 99
>>> def tester():
...     def nested():
...         nonlocal spam   # Переменная должна быть внутри def, а не в модуле!
...         print('Current=', spam)
...         spam += 1
...     return nested
...
SyntaxError: no binding for nonlocal 'spam' found

Интерпретатор определяет местоположение нелокальных имен в момент создания функции, а не в момент ее вызова.

Чем заменить unlocal?

Глобальная переменная

Минус - один (единственный) экземпляр переменной для хранения информации.

global нужно написать в обеих функциях.

>>> def tester(start):
...     global state        # Переместить в область видимости модуля
...     state = start       # global позволяет изменять переменные, находящиеся
...     def nested(label):  # в области видимости модуля
...         global state
...         print(label, state)
...         state += 1
...     return nested
...
>>> F = tester(0)
>>> F('spam')               # Каждый вызов будет изменять глобальную state
spam 0
>>> F('eggs')
eggs 1
>>> G = tester(42)          # Сбросит значение единственной копии state
>>> G('toast')              # в глобальной области видимости
toast 42
>>> G('bacon')
bacon 43
>>> F('ham')                # Ой – значение моего счетчика было затерто!
ham 44

Классы

Код лучше читается!

>>> class tester:                   # Альтернативное решение на основе классов
...     def __init__(self, start):  # Конструктор объекта,
...         self.state = start      # сохранение информации в новом объекте
...     def nested(self, label):
...         print(label, self.state) # Явное обращение к информации
...         self.state += 1          # Изменения всегда допустимы
...
>>> F = tester(0)           # Создаст экземпляр класса, вызовет __init__
>>> F.nested('spam')        # Ссылка на F будет передана в аргументе self
spam 0
>>> F.nested('ham')
ham 1
>>> G = tester(42)          # Каждый экземпляр получает свою копию информации
>>> G.nested('toast')       # Изменения в одном объекте не сказываются на других
toast 42
>>> G.nested('bacon')
bacon 43
>>> F.nested('eggs')        # В объекте F сохранилась прежняя информация
eggs 2
>>> F.state                 # Информация может быть получена за пределами класса
3

Избавимся от функции nested, чтобы мы могли писать прямо F('spam'), переопределив функцию __call__

>>> class tester:
...     def __init__(self, start):
...         self.state = start
...     def __call__(self, label):      # Вызывается при вызове экземпляра
...         print(label, self.state)    # Благодаря этому отпадает
...         self.state += 1             # необходимость в методе .nested()
...
>>> H = tester(99)
>>> H('juice')                          # Вызовет метод __call__
juice 99
>>> H('pancakes')
pancakes 100

Атрибуты функций

>>> def tester(start):
...     def nested(label):
...         print(label, nested.state)  # nested – объемлющая область видимости
...         nested.state += 1           # Изменит атрибут, а не значение имени nested
...         nested.state = start        # Инициализация после создания функции
...     return nested
...
>>> F = tester(0)
>>> F('spam')           # F – это функция 'nested'
spam 0                  # с присоединенным атрибутом state
>>> F('ham')
ham 1
>>> F.state             # Атрибут state доступен за пределами функции
2
>>>
>>> G = tester(42)      # G имеет собственный атрибут state,
>>> G('eggs')           # отличный от одноименного атрибута функции F
eggs 42
>>> F('ham')
ham 2

имя функции nested является локальной переменной в области видимости функции tester, включающей имя nested, – на это имя можно ссылаться и внутри функции nested. Кроме того, здесь используется то обстоятельство, что изменение самого объекта не является операцией присваивания, - операция увеличения значения nested.state изменяет часть объекта, на который ссылается имя nested, а не саму переменную с именем nested. Поскольку во вложенной функции не выполняется операция присваивания, необходимость в инструкции nonlocal отпадает сама собой.

Контрольные вопросы

  1. Что выведет следующий фрагмент и почему?

    >>> X = 'Spam'
    >>> def func():
    ...     print(X)
    ...
    >>> func()
    
  2. Что выведет следующий фрагмент и почему?

    >>> X = 'Spam'
    >>> def func():
    ...     X ='NI!'
    ...
    >>> func()
    >>> print(X)
    
  3. Что выведет следующий фрагмент и почему?

    >>> X = 'Spam'
    >>> def func():
    ...     X = 'NI'
    ...     print(X)
    ...
    >>> func()
    >>> print(X)
    
  4. Что выведет следующий фрагмент и почему?

    >>> X = 'Spam'
    >>> def func():
    ...     global X
    ...     X = 'NI'
    ...
    >>> func()
    >>> print(X)
    
  5. Что выведет следующий фрагмент и почему?

    >>> X = 'Spam'
    >>> def func():
    ...     X = 'NI'
    ...     def nested():
    ...         print(X)
    ...     nested()
    ...
    >>> func()
    >>> X
    
  6. Что выведет следующий фрагмент и почему?

    >>> def func():
    ...     X = 'NI'
    ...     def nested():
    ...         nonlocal X
    ...         X = 'Spam'
    ...     nested()
    ...     print(X)
    ...
    >>> func()
    
  7. Назовите три или более способов в языке Python сохранять информацию о состоянии в функциях.

Ответы

  1. В данном случае будет выведена строка 'Spam', потому что функция обращается к глобальной переменной в объемлющем модуле (если внутри функции переменной не присваивается значение, она интерпретируется как глобальная).
  2. В данном случае снова будет выведена строка 'Spam', потому что операция присваивания внутри функции создает локальную переменную и тем самым скрывает глобальную переменную с тем же именем. Инструкция print находит неизмененную переменную в глобальной области видимости.
  3. Будет выведена последовательность символов 'Ni' в одной строке и 'Spam' - в другой, потому что внутри функции инструкция print найдет локальную переменную, а за ее пределами – глобальную.
  4. На этот раз будет выведена строка 'Ni', потому что объявление global предписывает выполнять присваивание внутри функции переменной, находящейся в глобальной области видимости объемлющего модуля.
  5. В этом случае снова будет выведена последовательность символов 'Ni' в одной строке и 'Spam' – в другой, потому что инструкция print во вложенной функции отыщет имя в локальной области видимости объемлющей функции, а инструкция print в конце фрагмента отыщет имя в глобальной области видимости.
  6. Этот фрагмент выведет строку 'Spam', так как инструкция nonlocal (доступная в Python 3.0, но не в 2.6) означает, что операция присваивания внутри вложенной функции изменит переменную X в локальной области видимости объемлющей функции. Без этой инструкции операция присваивания классифицировала бы переменную X как локальную для вложенной функции и создала бы совершенно другую переменную - в этом случае приведенный фрагмент вывел бы строку 'NI'.
  7. Поскольку значения локальных переменных исчезают, когда функция возвращает управление, то информацию о состоянии в языке Python можно сохранять в глобальных переменных, для вложенных функций - в области видимости объемлющих функций, а также посредством аргументов со значениями по умолчанию. Иногда можно использовать прием, основанный на сохранении информации в атрибутах, присоединяемых к функциям, вместо использования области видимости объемлющей функции. Альтернативный способ заключается в использовании классов и приемов ООП, который обеспечивает лучшую поддержку возможности сохранения ин- формации о состоянии, чем любой из предыдущих приемов, основанных на использовании областей видимости, потому что этот способ делает сохранение явным, позволяя выполнять присваивание значений атрибутам.

results matching ""

    No results matching ""