Функции подробнее
Описано на основе
- Лутц, Изучаем 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 отпадает сама собой.
Контрольные вопросы
Что выведет следующий фрагмент и почему?
>>> X = 'Spam' >>> def func(): ... print(X) ... >>> func()
Что выведет следующий фрагмент и почему?
>>> X = 'Spam' >>> def func(): ... X ='NI!' ... >>> func() >>> print(X)
Что выведет следующий фрагмент и почему?
>>> X = 'Spam' >>> def func(): ... X = 'NI' ... print(X) ... >>> func() >>> print(X)
Что выведет следующий фрагмент и почему?
>>> X = 'Spam' >>> def func(): ... global X ... X = 'NI' ... >>> func() >>> print(X)
Что выведет следующий фрагмент и почему?
>>> X = 'Spam' >>> def func(): ... X = 'NI' ... def nested(): ... print(X) ... nested() ... >>> func() >>> X
Что выведет следующий фрагмент и почему?
>>> def func(): ... X = 'NI' ... def nested(): ... nonlocal X ... X = 'Spam' ... nested() ... print(X) ... >>> func()
Назовите три или более способов в языке Python сохранять информацию о состоянии в функциях.
Ответы
- В данном случае будет выведена строка 'Spam', потому что функция обращается к глобальной переменной в объемлющем модуле (если внутри функции переменной не присваивается значение, она интерпретируется как глобальная).
- В данном случае снова будет выведена строка 'Spam', потому что операция присваивания внутри функции создает локальную переменную и тем самым скрывает глобальную переменную с тем же именем. Инструкция print находит неизмененную переменную в глобальной области видимости.
- Будет выведена последовательность символов 'Ni' в одной строке и 'Spam' - в другой, потому что внутри функции инструкция print найдет локальную переменную, а за ее пределами – глобальную.
- На этот раз будет выведена строка 'Ni', потому что объявление global предписывает выполнять присваивание внутри функции переменной, находящейся в глобальной области видимости объемлющего модуля.
- В этом случае снова будет выведена последовательность символов 'Ni' в одной строке и 'Spam' – в другой, потому что инструкция print во вложенной функции отыщет имя в локальной области видимости объемлющей функции, а инструкция print в конце фрагмента отыщет имя в глобальной области видимости.
- Этот фрагмент выведет строку 'Spam', так как инструкция nonlocal (доступная в Python 3.0, но не в 2.6) означает, что операция присваивания внутри вложенной функции изменит переменную X в локальной области видимости объемлющей функции. Без этой инструкции операция присваивания классифицировала бы переменную X как локальную для вложенной функции и создала бы совершенно другую переменную - в этом случае приведенный фрагмент вывел бы строку 'NI'.
- Поскольку значения локальных переменных исчезают, когда функция возвращает управление, то информацию о состоянии в языке Python можно сохранять в глобальных переменных, для вложенных функций - в области видимости объемлющих функций, а также посредством аргументов со значениями по умолчанию. Иногда можно использовать прием, основанный на сохранении информации в атрибутах, присоединяемых к функциям, вместо использования области видимости объемлющей функции. Альтернативный способ заключается в использовании классов и приемов ООП, который обеспечивает лучшую поддержку возможности сохранения ин- формации о состоянии, чем любой из предыдущих приемов, основанных на использовании областей видимости, потому что этот способ делает сохранение явным, позволяя выполнять присваивание значений атрибутам.