Что такое замыкание? В простейшем определении, замыкание - это функция, которая запоминает и хранит доступ к переменным из охватывающей её области видимости, даже когда эта функция вызывается за пределами этой области. Представьте себе, что вы создаёте функцию, которая не просто выполняет какие-то действия, но и "помнит" контекст, в котором была создана. В Python замыкания кажутся почти магическими - внутренняя функция каким-то образом сохраняет связь с переменными из внешней функции, даже после того, как внешняя функция завершила своё выполнение. Но никакой магии здесь нет - всё это результат особенностей работы областей видимости и времени жизни объектов в языке.
Замыкания становятся особенно ценными, когда мы говорим о функциональном программировании. Функциональный подход предполагает использование функций как основных строительных блоков программы, и замыкания расширяют эту концепцию, давая функциям возможность нести с собой часть окружения. С помощью замыканий можно реализовать множество интересных паттернов: от простого сохранения состояния между вызовами функции до создания "фабрик функций" и реализации мощных декораторов. Умелое использование замыканий позволяет писать более компактный, выразительный и элегантный код, избегая необходимости создавать целые классы для простых задач.
Основы
Чтобы по-настоящему понять замыкания в Python, нужно сначала разобраться с несколькими фундаментальными концепциями языка, которые делают замыкания возможными.
Функции как объекты первого класса
В Python функции — это объекты первого класса. Это значит, что с функциями можно обращаться так же, как с любыми другими объектами: их можно присваивать переменным, передавать в качестве аргументов, возвращать из других функций и даже хранить в структурах данных.
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| def приветствие(имя):
return f"Привет, {имя}!"
# Функцию можно присвоить переменной
поздороваться = приветствие
# Теперь можно вызвать функцию через новое имя
print(поздороваться("Андрей")) # Выведет: Привет, Андрей!
# Функцию можно передать как аргумент
def выполнить_функцию(функция, аргумент):
return функция(аргумент)
print(выполнить_функцию(приветствие, "Елена")) # Привет, Елена!
# Функцию можно вернуть из другой функции
def получить_функцию_приветствия():
return приветствие
новое_приветствие = получить_функцию_приветствия()
print(новое_приветствие("Сергей")) # Привет, Сергей! |
|
Эта особенность Python играет ключевую роль в создании замыканий, поскольку без возможности возвращать функции из функций замыкания были бы невозможны.
Вложенные функции
Вторая важная концепция — вложенные функции, то есть функции, определённые внутри других функций. В Python можно создавать сколько угодно уровней вложенности.
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
| def внешняя_функция(x):
# Переменная внешней функции
сообщение = f"Значение x: {x}"
def внутренняя_функция():
# Внутренняя функция имеет доступ к переменным внешней
print(сообщение)
# Вызываем внутреннюю функцию
внутренняя_функция()
внешняя_функция(10) # Выведет: Значение x: 10 |
|
В этом примере внутренняя_функция имеет доступ к переменной сообщение из окружающей её внешняя_функция. Этот доступ основан на лексическом окружении функции.
Области видимости в Python
Python использует систему областей видимости LEGB для определения доступа к переменным:
Local (Локальная) — переменные, определённые внутри текущей функции.
Enclosing (Охватывающая) — переменные из внешних функций.
Global (Глобальная) — переменные модуля.
Built-in (Встроенная) — встроенные имена Python (print, len и т.д.).
Когда интерпретатор встречает переменную, он ищет её последовательно в этих областях. Для замыканий особенно важна охватывающая область (Enclosing).
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
| x = "глобальная"
def внешняя():
x = "охватывающая"
def внутренняя():
x = "локальная"
print(f"Внутри внутренней: {x}")
внутренняя()
print(f"Внутри внешней: {x}")
внешняя()
print(f"В глобальной области: {x}") |
|
Этот код выведет:
| Python | 1
2
3
| Внутри внутренней: локальная
Внутри внешней: охватывающая
В глобальной области: глобальная |
|
Различия между замыканиями и обычными функциями
Обычная функция имеет доступ только к своим локальным переменным и глобальным переменным. Когда функция завершает работу, все её локальные переменные уничтожаются. Замыкание же — функция, которая "запоминает" значения переменных из своей охватывающей области даже после того, как внешняя функция завершила выполнение.
| Python | 1
2
3
4
5
6
7
8
9
10
| def создать_умножитель(множитель):
def умножитель(число):
return число * множитель # множитель из внешней функции
return умножитель
удвоить = создать_умножитель(2)
утроить = создать_умножитель(3)
print(удвоить(5)) # 10
print(утроить(5)) # 15 |
|
В этом примере удвоить и утроить — замыкания. Каждое из них сохраняет своё значение переменной множитель, с которым было создано, хотя функция создать_умножитель уже давно завершилась.
Лексическое окружение в Python
Лексическое окружение определяет, какие переменные доступны функции в момент её создания, а не вызова. Это ключевая концепция для понимания замыканий. Когда в Python создаётся вложенная функция, она "захватывает" ссылки на переменные из окружающих областей, которые использует. Эти переменные становятся частью замыкания.
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
| def создать_счётчик():
счётчик = 0
def увеличить():
nonlocal счётчик # Объявляем, что используем переменную из внешней области
счётчик += 1
return счётчик
return увеличить
мой_счётчик = создать_счётчик()
print(мой_счётчик()) # 1
print(мой_счётчик()) # 2
print(мой_счётчик()) # 3 |
|
Ключевое слово nonlocal появилось в Python 3 и позволяет изменять переменные из внешних областей видимости, что критически важно для многих применений замыканий.
Особенности поиска имён и их влияние на замыкания
В Python существует интересная особенность — если вы просто используете переменную из внешней области (читаете её значение), всё работает как ожидается. Но если вы пытаетесь изменить такую переменную без объявления её как nonlocal, Python создаст новую локальную переменную с тем же именем.
| Python | 1
2
3
4
5
6
7
8
9
10
11
| def проблемный_счётчик():
счётчик = 0
def увеличить():
# Без nonlocal будет ошибка: UnboundLocalError
счётчик += 1 # Python считает, что счётчик - локальная переменная
return счётчик
return увеличить
# Вызов этой функции приведёт к ошибке |
|
Эта особенность часто вызывает путаницу у начинающих работать с замыканиями в Python.
Замыкания в сравнении с другими языками
Концепция замыканий присутствует во многих языках программирования, но реализация может отличаться.
В JavaScript замыкания используются повсеместно и работают похожим образом:
| JavaScript | 1
2
3
4
5
6
7
8
| function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
console.log(double(5)); // 10 |
|
Однако в JavaScript нет необходимости в специальном ключевом слове типа nonlocal для изменения переменных из внешней области.
Lisp, один из старейших языков программирования, также поддерживает замыкания, и они являются фундаментальной частью языка:
| Lisp | 1
2
3
4
5
| (defun make-adder (x)
(lambda (y) (+ x y)))
(setq add-5 (make-adder 5))
(funcall add-5 3) ; => 8 |
|
Знание основ работы замыканий даёт прочный фундамент для понимания более сложных примеров и практических применений этой мощной концепции в Python.
Различия между замыканиями и обычными функциями
Для полного понимания замыканий необходимо чётко различать их и обычные функции в Python. Главное отличие заключается в том, что замыкание сохраняет ссылки на переменные из внешней области видимости, тогда как обычная функция работает только с локальными переменными и параметрами.
| Python | 1
2
3
4
5
6
7
8
9
| # Обычная функция
def simple_function(x):
return x * 2
# Функция, возвращающая замыкание
def make_multiplier(factor):
def multiplier(x):
return x * factor # использует переменную из внешней области
return multiplier |
|
В примере выше simple_function — это обычная функция, которая принимает аргумент и возвращает его удвоенное значение. А make_multiplier возвращает замыкание, которое запоминает значение factor из внешней функции.
Ещё одно важное отличие состоит в том, что замыкания создаются динамически во время выполнения программы, а обычные функции определяются статически.
Лексическое окружение в Python
Для работы замыканий критически важно понятие лексического окружения. Это механизм, который определяет, как интерпретатор разрешает имена переменных. Лексическое окружение в Python представляет собой словарь, связывающий имена с объектами. Когда создаётся функция, она не только запоминает свой код, но и захватывает ссылку на лексическое окружение, в котором была определена.
| Python | 1
2
3
4
5
6
7
8
| def outer_function():
x = 10
def inner_function():
print(x) # x из окружения outer_function
return inner_function
closure = outer_function()
closure() # Выведет: 10 |
|
В этом примере inner_function имеет доступ к переменной x из окружения outer_function. Даже когда outer_function завершает работу, inner_function сохраняет доступ к x.
При желании можно проверить, какие переменные замыкание "захватило" из внешней функции:
| Python | 1
| closure.__closure__[0].cell_contents # Получим 10 |
|
Области видимости LEGB и замыкания
Python использует правило LEGB для поиска переменных:
Local (локальная): переменные внутри функции.
Enclosing (объемлющая): переменные из объемлющих функций.
Global (глобальная): переменные уровня модуля.
Built-in (встроенная): встроенные имена Python.
Замыкания работают благодаря уровню "E" — они могут обращаться к переменным из объемлющих функций.
Интересный нюанс заключается в модификации переменных из охватывающей области. До Python 3 это было невозможно без особых трюков. С появлением ключевого слова nonlocal в Python 3 модификация стала возможной:
| Python | 1
2
3
4
5
6
7
8
9
10
11
| def counter_factory():
count = 0
def increment():
nonlocal count # указываем, что хотим изменить переменную из внешней области
count += 1
return count
return increment
counter = counter_factory()
print(counter()) # 1
print(counter()) # 2 |
|
Без ключевого слова nonlocal попытка изменить count привела бы к созданию новой локальной переменной с тем же именем внутри функции increment.
Особенности поиска имен в Python
Поиск имен в Python имеет ряд тонкостей, влияющих на работу замыканий:
1. Позднее связывание — Python ищет значение переменной в момент вызова функции, а не в момент её определения. Это может приводить к неожиданным результатам:
| Python | 1
2
3
4
5
6
7
8
9
10
| def create_functions():
functions = []
for i in range(3):
def func():
return i
functions.append(func)
return functions
funcs = create_functions()
[f() for f in funcs] # [2, 2, 2], а не [0, 1, 2], как можно ожидать |
|
Это происходит потому, что все три функции ссылаются на одну и ту же переменную i, которая имеет значение 2 на момент вызова функций.
2. Замыкание захватывает ссылки на переменные, а не их значения — это прямо связано с предыдущим пунктом. Чтобы зафиксировать значение переменной на момент создания функции, можно использовать параметры по умолчанию:
| Python | 1
2
3
4
5
6
7
8
9
10
| def create_functions_fixed():
functions = []
for i in range(3):
def func(i=i): # i=i создаёт локальную копию i
return i
functions.append(func)
return functions
funcs = create_functions_fixed()
[f() for f in funcs] # [0, 1, 2] — теперь всё работает как ожидалось |
|
Замыкания в сравнении с другими языками
Python не единственный язык, поддерживающий концепцию замыканий. Понимание различий в реализации может помочь лучше осознать их работу в Python.
В JavaScript замыкания являются фундаментальной частью языка и работают схожим образом:
| JavaScript | 1
2
3
4
5
| function makeMultiplier(factor) {
return function(x) {
return x * factor;
};
} |
|
Однако JavaScript имеет особенности со связыванием this, которые делают работу с замыканиями немного более сложной в некоторых контекстах. В функциональных языках, таких как Lisp, замыкания служат краеугольным камнем для построения абстракций. Lisp был одним из первых языков, реализовавших замыкания, и влиял на их дизайн в других языках. В Ruby замыкания реализуются через блоки, proc и lambda, которые имеют некоторые синтаксические и поведенческие отличия от Python.
Python занимает промежуточное положение — замыкания реализованы просто и интуитивно, но с некоторыми ограничениями по сравнению с языками, изначально спроектированными для функционального программирования.
Теперь, когда мы разобрались с основами механизма замыканий, давайте рассмотрим, как они реально работают "под капотом" и какие проблемы могут возникнуть при их использовании.
С помощью замыкания напишите генератор очередного простого числа из списка С помощью замыкания напишите генератор очередного простого числа из заданного списка.
lst = ; func... Используя замыкания функций, объявить внутреннюю функцию Доброго дня! Не могу решить задачку.
На вход программы поступает две строки: первая с тегом,... Замыкания Доброго времени суток. Имеется такая задача на замыкания:
Используя замыкания функций, объявите... Используя замыкания функций, определите вложенную функцию Используя замыкания функций, определите вложенную функцию, которая бы увеличивала
значение...
Механизм работы замыканий
Теперь, когда мы познакомились с основными концепциями, давайте глубже погрузимся в то, как замыкания на самом деле функционируют в Python. Чтобы лучше понять это, сначала дадим формальное определение. Замыкание в Python — это специфическая комбинация функции и окружающей её лексической среды. Точнее, это внутренняя функция, которая возвращается из внешней функции и сохраняет доступ к переменным этой внешней функции, даже когда она вызывается вне контекста своего создания.
Условия создания истинного замыкания
Не каждая вложенная функция автоматически становится замыканием. Чтобы функция классифицировалась как замыкание, должны выполняться несколько условий:
1. Должна существовать вложенная функция (функция внутри другой функции).
2. Вложенная функция должна ссылаться на значение из внешней функции.
3. Внешняя функция должна возвращать вложенную функцию.
Если хотя бы одно из этих условий не выполняется, мы имеем дело с обычной вложенной функцией, а не с замыканием.
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| def not_a_closure():
x = 10
def inner():
print("Это просто вложенная функция")
inner() # вызываем функцию, а не возвращаем её
def also_not_a_closure():
def inner():
print("Здесь нет захвата внешних переменных")
return inner # возвращаем функцию, но без захвата внешних переменных
def this_is_a_closure():
x = 10
def inner():
print(f"Замыкание захватило x = {x}")
return inner # возвращаем функцию, которая захватывает x |
|
Примеры простых замыканий
Рассмотрим несколько практических примеров замыканий:
| Python | 1
2
3
4
5
6
7
8
9
10
| def power_factory(exponent):
def power_function(base):
return base ** exponent
return power_function
square = power_factory(2)
cube = power_factory(3)
print(square(4)) # 16
print(cube(4)) # 64 |
|
В этом примере power_factory создаёт и возвращает функции для возведения числа в заданную степень. Функции square и cube являются замыканиями, захватившими переменную exponent.
Более сложный пример — функция, которая запоминает своё последнее значение:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
| def make_averager():
series = []
def averager(new_value):
series.append(new_value)
return sum(series) / len(series)
return averager
avg = make_averager()
print(avg(10)) # 10.0
print(avg(15)) # 12.5
print(avg(20)) # 15.0 |
|
Здесь замыкание averager хранит историю всех переданных значений в списке series и вычисляет их среднее.
Визуализация работы замыканий через диаграммы памяти
Для наглядного понимания того, как замыкания работают внутри Python, представим структуру памяти для следующего кода:
| Python | 1
2
3
4
5
6
7
8
| def outer(x):
y = 10
def inner():
return x + y
return inner
closure1 = outer(5)
closure2 = outer(10) |
|
В памяти Python это выглядит примерно так:
1. При вызове outer(5) создаётся фрейм с локальными переменными x=5 и y=10.
2. Создаётся функция inner, которая захватывает ссылки на x и y.
3. Ссылка на inner возвращается и присваивается переменной closure1.
4. Фрейм outer должен быть удалён, но переменные x и y всё ещё нужны для inner.
5. Python создаёт "ячейки" (cell objects), которые содержат значения x и y.
6. Функция inner хранит ссылки на эти ячейки в своём атрибуте __closure__.
При создании closure2 процесс повторяется, но с x=10, так что closure1 и closure2 хранят разные значения в своих замкнутых переменных. Python использует специальный тип данных называемый "ячейкой" (cell) для реализации замыканий. Ячейка содержит указатель на объект, что позволяет нескольким областям видимости (например, внутренней и внешней функциям) ссылаться на один и тот же объект.
| Python | 1
2
3
4
5
| # Проверка содержимого замыкания
print(closure1.__closure__[0].cell_contents) # 5
print(closure1.__closure__[1].cell_contents) # 10
print(closure2.__closure__[0].cell_contents) # 10
print(closure2.__closure__[1].cell_contents) # 10 |
|
Распространенные ошибки при создании замыканий
При работе с замыканиями особенно важно помнить о нескольких распространённых ошибках:
1. Проблема последнего значения в цикле:
| Python | 1
2
| def create_multipliers():
return [lambda x: i * x for i in range(5)] |
|
Здесь все функции будут использовать последнее значение i (4), потому что замыкания захватывают ссылки на переменные, а не их значения на момент создания.
2. Попытка изменить не-локальные переменные без nonlocal:
| Python | 1
2
3
4
5
6
| def counter():
count = 0
def increment():
count += 1 # Ошибка! Python считает count локальной переменной
return count
return increment |
|
3. Захват изменяемых объектов по ссылке:
| Python | 1
2
3
4
5
6
| def append_factory():
data = []
def appender(item):
data.append(item)
return data
return appender |
|
Здесь все созданные функции будут использовать один и тот же список data, что может быть как преимуществом, так и недостатком в зависимости от ваших целей.
Отладка кода с использованием замыканий: подходы и инструменты
Отладка кода, использующего замыкания, может представлять определённые сложности. Особенность замыканий в том, что захваченные переменные живут в специальном хранилище (cells), доступ к которому нельзя получить обычными средствами. Рассмотрим эффективные стратегии для отладки кода с замыканиями. Самым простым способом проверить содержимое захваченных переменных является использование атрибута __closure__:
| Python | 1
2
3
4
5
6
7
8
| def outer(x):
def inner():
return x
return inner
closure = outer(42)
# Доступ к захваченным переменным
print(closure.__closure__[0].cell_contents) # 42 |
|
Ещё один полезный прием при отладке замыканий — использование встроенного модуля inspect. Он предоставляет инструменты для исследования живых объектов, включая замыкания:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| import inspect
def create_closure(x, y):
def inner(z):
return x + y + z
return inner
my_closure = create_closure(10, 20)
# Получение исходного кода функции
print(inspect.getsource(my_closure))
[H2]Результат: не исходный код create_closure, а только inner[/H2]
# Получение имён свободных переменных в функции
print(my_closure.__code__.co_freevars) # ('x', 'y') |
|
В сложных случаях может потребоваться изменение захваченных переменных для изоляции проблемы. Это невозможно сделать напрямую через __closure__, но можно модифицировать сам код, добавив в замыкание дополнительные методы для работы с захваченными переменными:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| def debuggable_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
# Добавляем функцию для отладки
def debug_get_count():
return count
increment.debug_get_count = debug_get_count
return increment
counter = debuggable_counter()
print(counter()) # 1
print(counter.debug_get_count()) # 1 |
|
Стандартные отладчики Python, такие как pdb или IDE-интегрированные дебаггеры, не всегда хорошо справляются с визуализацией замыканий. Иногда приходится применять специальные техники:
| Python | 1
2
3
4
5
6
7
8
| def factory(x):
def closure(y):
import pdb; pdb.set_trace() # Точка останова внутри замыкания
return x + y
return closure
func = factory(10)
result = func(5) # Здесь активируется отладчик |
|
Влияние сборщика мусора на работу с замыканиями
Замыкания создают интересную ситуацию для сборщика мусора Python. Когда внешняя функция завершает выполнение, её фрейм обычно подлежит удалению. Однако если была захвачена переменная для замыкания, Python должен сохранить эту переменную в памяти. Рассмотрим пример, демонстрирующий взаимодействие замыканий со сборщиком мусора:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| import weakref
import gc
def outer():
# Создаём большой объект
big_list = [0] * 1000000
# Создаём слабую ссылку для отслеживания
weak_ref = weakref.ref(big_list)
def inner():
# Не используем big_list напрямую
return "Closure exists"
# Возвращаем замыкание и слабую ссылку
return inner, weak_ref
closure, weak_ref = outer()
# Проверяем, существует ли еще большой список
print(weak_ref() is not None) # True - список еще не собран мусорщиком
# Удаляем замыкание
del closure
gc.collect() # Принудительно запускаем сборку мусора
# Теперь список должен быть удален
print(weak_ref() is not None) # False - список собран мусорщиком |
|
Замыкания могут привести к неожиданным утечкам памяти, если они захватывают большие структуры данных, которые в действительности не нужны. Например:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| def create_processor(data):
# Большой набор данных
processed = [item * 2 for item in data]
def get_item(index):
return processed[index]
return get_item
# Создаём процессор для большого списка
big_data = list(range(1000000))
processor = create_processor(big_data)
# big_data можно удалить, так как он больше не нужен
del big_data
# Но processed остаётся в памяти из-за замыкания! |
|
Для решения этой проблемы можно использовать паттерн "клонирования данных", когда замыкание захватывает только ту часть данных, которая ему действительно нужна:
| Python | 1
2
3
4
5
6
7
8
9
10
11
| def create_efficient_processor(data):
# Сохраняем только необходимые вычисленные данные
processed = [item * 2 for item in data]
# Освобождаем оригинальные данные от внешних ссылок
data = None
def get_item(index):
return processed[index]
return get_item |
|
При работе с замыканиями также стоит помнить о циклических ссылках. Если замыкание захватывает объект, который сам содержит ссылку на замыкание, создаётся цикл, который может задержать освобождение памяти:
| Python | 1
2
3
4
5
6
7
8
9
10
| def create_circular_reference():
container = {}
def closure():
return container
# Создаём циклическую ссылку
container['self_reference'] = closure
return closure |
|
В этом случае сборщик мусора Python может определить и удалить такие циклы, но это происходит не сразу, а только при запуске полного цикла сборки мусора.
Практическое применение
Теория замыканий становится особенно привлекательной, когда мы переходим к их практическому применению. Давайте рассмотрим основные сценарии использования замыканий, которые могут значительно упростить разработку.
Сохранение состояния
Одно из главных преимуществ замыканий - возможность сохранять состояние между вызовами функции без использования глобальных переменных или создания классов. Это особенно полезно, когда нужно отслеживать некоторое внутреннее состояние:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
| def cumulative_average():
values = []
def average(value):
values.append(value)
return sum(values) / len(values)
return average
avg = cumulative_average()
print(avg(10)) # 10.0
print(avg(20)) # 15.0
print(avg(30)) # 20.0 |
|
В этом примере функция average сохраняет все переданные значения в списке values и вычисляет их среднее арифметическое. При каждом вызове список пополняется, что позволяет функции "помнить" предыдущие вызовы.
Инкапсуляция данных
Замыкания обеспечивают простой способ инкапсуляции данных - один из ключевых принципов объектно-ориентированного программирования. При этом инкапсуляция достигается без создания полноценных классов:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| def make_account(initial_balance=0):
balance = initial_balance
def deposit(amount):
nonlocal balance
balance += amount
return balance
def withdraw(amount):
nonlocal balance
if amount > balance:
raise ValueError("Недостаточно средств")
balance -= amount
return balance
def get_balance():
return balance
# Возвращаем словарь с доступными операциями
return {
'deposit': deposit,
'withdraw': withdraw,
'balance': get_balance
}
# Использование
account = make_account(1000)
print(account['balance']()) # 1000
account['deposit'](500)
print(account['balance']()) # 1500
account['withdraw'](200)
print(account['balance']()) # 1300 |
|
В этом примере переменная balance инкапсулирована внутри замыкания и недоступна напрямую извне. Доступ к ней возможен только через предоставленные функции.
Фабрики функций
Замыкания естественным образом становятся "фабриками функций" - механизмами для создания специализированных функций с предустановленными параметрами:
| Python | 1
2
3
4
5
6
7
8
9
10
| def make_formatter(prefix, suffix):
def format_string(string):
return f"{prefix}{string}{suffix}"
return format_string
html_paragraph = make_formatter("<p>", "</p>")
bold_text = make_formatter("<b>", "</b>")
print(html_paragraph("Это параграф")) # <p>Это параграф</p>
print(bold_text("Жирный текст")) # <b>Жирный текст</b> |
|
Фабрики функций особенно полезны, когда нужно создать семейство похожих функций, которые отличаются только некоторыми параметрами.
Создание счетчиков и аккумуляторов
Счетчики - классический пример использования замыканий. Они эффективно демонстрируют способность замыканий сохранять и изменять состояние:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| def make_counter(start=0, step=1):
count = start - step # Компенсируем первое увеличение
def counter():
nonlocal count
count += step
return count
return counter
count_from_ten = make_counter(10)
count_by_twos = make_counter(0, 2)
print(count_from_ten()) # 10
print(count_from_ten()) # 11
print(count_from_ten()) # 12
print(count_by_twos()) # 0
print(count_by_twos()) # 2
print(count_by_twos()) # 4 |
|
Более сложный пример - аккумулятор, который может собирать статистику или применять функции к накопленным данным:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| def make_accumulator(func=sum):
items = []
def accumulate(item=None):
if item is not None:
items.append(item)
return func(items)
return accumulate
sum_acc = make_accumulator()
max_acc = make_accumulator(max)
sum_acc(5)
sum_acc(10)
sum_acc(15)
print(sum_acc()) # 30
max_acc(3)
max_acc(7)
max_acc(2)
print(max_acc()) # 7 |
|
Предоставление функций обратного вызова
Замыкания идеально подходят для создания функций обратного вызова (колбэков), которые сохраняют контекст вызова. Это особенно полезно при работе с графическими интерфейсами и асинхронным программированием:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import tkinter as tk
def make_button_handler(message):
def handler():
print(f"Нажата кнопка: {message}")
return handler
root = tk.Tk()
root.title("Пример замыканий с GUI")
button1 = tk.Button(root, text="Кнопка 1", command=make_button_handler("Первая"))
button2 = tk.Button(root, text="Кнопка 2", command=make_button_handler("Вторая"))
button1.pack()
button2.pack()
# root.mainloop() # Раскомментируйте для запуска GUI |
|
В этом примере каждая кнопка получает уникальную функцию-обработчик, которая "помнит" сообщение, связанное с конкретной кнопкой.
Замыкания также эффективно используются в асинхронном программировании для сохранения контекста между различными частями асинхронного кода:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import asyncio
def create_async_task(task_id):
async def task():
print(f"Задача {task_id} начата")
await asyncio.sleep(1) # Имитация асинхронной операции
print(f"Задача {task_id} завершена")
return f"Результат задачи {task_id}"
return task
# Пример использования
async def main():
tasks = [create_async_task(i) for i in range(3)]
results = await asyncio.gather(*(task() for task in tasks))
print(results)
# asyncio.run(main()) # Раскомментируйте для запуска |
|
Замыкания в асинхронном программировании
Асинхронное программирование — одна из областей, где замыкания демонстрируют свою исключительную полезность. С появлением asyncio в Python замыкания стали популярным инструментом для управления контекстом между асинхронными операциями. Рассмотрим более сложный пример, где замыкания помогают организовать повторные попытки выполнения асинхронных операций:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| import asyncio
import random
def create_retry_function(max_retries=3, delay=1):
async def retry_operation(operation, *args, **kwargs):
retries = 0
while True:
try:
return await operation(*args, **kwargs)
except Exception as e:
retries += 1
if retries > max_retries:
raise
wait_time = delay * (2 ** (retries - 1)) * (0.5 + random.random())
print(f"Попытка {retries} не удалась. Ожидание {wait_time:.2f}с")
await asyncio.sleep(wait_time)
return retry_operation
# Пример использования
async def fetch_data(url):
# Имитация запроса, который иногда завершается ошибкой
if random.random() < 0.7: # 70% вероятность ошибки
raise ConnectionError("Ошибка соединения")
return f"Данные с {url}"
async def main():
retry_fetch = create_retry_function(max_retries=4, delay=0.5)
try:
result = await retry_fetch(fetch_data, "https://example.com")
print(f"Успех: {result}")
except Exception as e:
print(f"Окончательная ошибка: {e}") |
|
В этом примере замыкание retry_operation захватывает параметры max_retries и delay, создавая настраиваемую функцию для повторных попыток выполнения операций с экспоненциальной задержкой.
Оптимизация запросов к базам данных
Замыкания могут существенно упростить работу с базами данных, особенно в контексте кэширования и построения запросов:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| def create_query_builder(connection_string):
import sqlite3
conn = sqlite3.connect(connection_string)
# Кэш для результатов запросов
query_cache = {}
def execute_query(query, params=None, use_cache=True):
# Формируем ключ для кэша
cache_key = (query, str(params)) if params else query
# Проверяем кэш, если разрешено
if use_cache and cache_key in query_cache:
print("Используем кэшированный результат")
return query_cache[cache_key]
# Выполняем запрос
cursor = conn.cursor()
if params:
cursor.execute(query, params)
else:
cursor.execute(query)
# Получаем результаты
results = cursor.fetchall()
# Сохраняем в кэш, если разрешено
if use_cache:
query_cache[cache_key] = results
return results
# Добавляем функцию очистки кэша
def clear_cache():
nonlocal query_cache
query_cache = {}
# Привязываем дополнительную функцию к основной
execute_query.clear_cache = clear_cache
return execute_query
# Пример использования
db_query = create_query_builder(":memory:")
# Создаем таблицу
db_query("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
# Добавляем данные
db_query("INSERT INTO users VALUES (?, ?)", (1, "Иван"))
db_query("INSERT INTO users VALUES (?, ?)", (2, "Мария"))
# Первый запрос выполняется и кэшируется
print(db_query("SELECT * FROM users WHERE id=?", (1,)))
# Второй идентичный запрос берется из кэша
print(db_query("SELECT * FROM users WHERE id=?", (1,)))
# Очищаем кэш
db_query.clear_cache() |
|
Этот пример демонстрирует создание построителя запросов, который автоматически кэширует результаты и позволяет вызывать операции над одним подключением к базе данных. Замыкание здесь не только сохраняет соединение с базой и кэш, но и предоставляет дополнительные функции через атрибуты основной функции.
Замыкания в разработке GUI
В контексте графических интерфейсов замыкания позволяют элегантно связывать состояние приложения с обработчиками событий:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| def create_counter_app():
import tkinter as tk
count = 0
# Создаем главное окно
root = tk.Tk()
root.title("Счетчик с замыканиями")
root.geometry("300x150")
# Создаем виджет для отображения счетчика
label = tk.Label(root, text=str(count), font=("Arial", 24))
label.pack(pady=20)
# Создаем обработчики для кнопок
def increment():
nonlocal count
count += 1
label.config(text=str(count))
def decrement():
nonlocal count
count -= 1
label.config(text=str(count))
# Создаем кнопки
btn_frame = tk.Frame(root)
btn_frame.pack()
minus_btn = tk.Button(btn_frame, text="-", command=decrement, font=("Arial", 16))
minus_btn.pack(side=tk.LEFT, padx=10)
plus_btn = tk.Button(btn_frame, text="+", command=increment, font=("Arial", 16))
plus_btn.pack(side=tk.LEFT, padx=10)
# Возвращаем функцию запуска приложения
def run():
root.mainloop()
return run
# Создаем и запускаем приложение
# counter_app = create_counter_app()
# counter_app() |
|
В этом примере замыкания increment и decrement имеют доступ к переменной count и виджету label, что позволяет им обновлять интерфейс при изменении состояния. Замыкание также возвращает функцию для запуска приложения, скрывая детали реализации.
Примеры из реальных проектов
Замыкания активно используются во многих популярных Python-библиотеках:
1. Flask и декораторы маршрутов — фреймворк Flask использует замыкания для реализации декораторов @app.route, которые связывают функции-обработчики с URL-шаблонами.
2. Pytest и фикстуры — система фикстур в Pytest во многом полагается на замыкания для сохранения состояния между тестами.
3. Requests и сессии — библиотека Requests использует замыкания для управления HTTP-сессиями и сохранения настроек.
Расширенные возможности
После знакомства с фундаментальными концепциями и практическим применением замыканий, пришло время углубиться в более продвинутые аспекты этого механизма. Python предлагает ряд сложных и мощных инструментов, которые расширяют возможности замыканий и позволяют создавать элегантные решения для нетривиальных задач.
nonlocal и замыкания
Ключевое слово nonlocal, появившееся в Python 3, значительно расширило возможности замыканий. До его появления модификация переменных из охватывающей области была затруднена. Сейчас же это стало стандартным паттерном:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| def make_multiplier_counter():
multiplier = 2
count = 0
def multiply_and_count(n):
nonlocal count, multiplier
count += 1
# Периодически увеличиваем множитель
if count % 10 == 0:
multiplier += 1
return n * multiplier, count, multiplier
return multiply_and_count
counter = make_multiplier_counter()
print(counter(5)) # (10, 1, 2) - число * множитель, счетчик, текущий множитель
print(counter(5)) # (10, 2, 2) |
|
В этом примере замыкание модифицирует сразу две переменные из внешней области видимости: count и multiplier. Без ключевого слова nonlocal это было бы невозможно, поскольку Python интерпретировал бы их как новые локальные переменные.
Интересный нюанс: когда переменная внешней области видимости ссылается на изменяемый объект (список, словарь), ключевое слово nonlocal не требуется для модификации содержимого самого объекта:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| def collection_processor():
data = []
def process(item):
# Модификация объекта не требует nonlocal
data.append(item)
return data
def clear():
# Переприсваивание требует nonlocal
nonlocal data
data = []
process.clear = clear
return process
processor = collection_processor()
print(processor(1)) # [1]
print(processor(2)) # [1, 2]
processor.clear()
print(processor(3)) # [3] |
|
Декораторы и замыкания
Декораторы в Python — это одно из наиболее мощных применений замыканий. Декоратор принимает функцию, модифицирует её поведение и возвращает новую функцию. По сути, декоратор — это функция высшего порядка, возвращающая замыкание. Рассмотрим реализацию декоратора, который измеряет время выполнения функции:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import time
import functools
def timing_decorator(func):
@functools.wraps(func) # Сохраняем метаданные оригинальной функции
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"Функция {func.__name__} выполнилась за {end_time - start_time:.6f} секунд")
return result
return wrapper
@timing_decorator
def slow_function(delay):
time.sleep(delay)
return "Операция завершена"
print(slow_function(0.5)) |
|
Замыкание wrapper захватывает функцию func и добавляет к ней функциональность измерения времени. Декоратор @functools.wraps помогает сохранить метаданные оригинальной функции (имя, докстринги и т.д.). Декораторы можно делать параметризируемыми, создавая замыкание дополнительного уровня:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| def repeat(n=3):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
results = []
for _ in range(n):
results.append(func(*args, **kwargs))
return results
return wrapper
return decorator
@repeat(5)
def generate_random():
import random
return random.random()
print(generate_random()) # Список из 5 случайных чисел |
|
Оптимизация кода с помощью замыканий
Замыкания могут значительно улучшить производительность кода, особенно в сценариях с частым доступом к данным или повторяющимися вычислениями. Один из классических примеров — мемоизация, техника кэширования результатов функции:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| def memoize(func):
cache = {}
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
# Добавим метод для очистки кэша
def clear_cache():
nonlocal cache
cache = {}
wrapper.clear_cache = clear_cache
return wrapper
@memoize
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# Без мемоизации функция была бы крайне неэффективной
print(fibonacci(35)) # Почти мгновенный результат благодаря кэшированию |
|
В этом примере замыкание wrapper сохраняет результаты вычислений в словаре cache. При повторном вызове с теми же аргументами функция просто возвращает сохраненный результат вместо перевычисления. Это особенно эффективно для рекурсивных функций, таких как вычисление чисел Фибоначчи.
Также замыкания могут использоваться для дорогостоящих инициализаций, которые должны выполняться только один раз:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| def lazy_loader(expensive_function):
result = None
initialized = False
def wrapper(*args, **kwargs):
nonlocal result, initialized
if not initialized:
result = expensive_function(*args, **kwargs)
initialized = True
return result
return wrapper
@lazy_loader
def load_large_dataset(path="data.csv"):
print("Загрузка большого набора данных...")
# В реальности здесь был бы код загрузки данных
return [i for i in range(1000)]
# Данные загружаются только при первом вызове
data = load_large_dataset()
# Последующие вызовы используют кэшированный результат
data_again = load_large_dataset() |
|
Замыкания и функциональное программирование в Python
Python, хотя и не является чисто функциональным языком, поддерживает множество концепций функционального программирования. Замыкания играют центральную роль в функциональной парадигме, позволяя реализовать такие паттерны, как композиция функций и функции высшего порядка.
Композиция функций — это объединение нескольких функций в одну, которая применяет их последовательно. Замыкания позволяют реализовать её элегантно:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| def compose(*functions):
def inner(x):
result = x
for func in reversed(functions):
result = func(result)
return result
return inner
# Примеры простых функций
def double(x): return x * 2
def square(x): return x ** 2
def increment(x): return x + 1
# Композиция функций
pipeline = compose(increment, square, double)
print(pipeline(3)) # (3 * 2)^2 + 1 = 37 |
|
Функции высшего порядка — это функции, которые принимают или возвращают другие функции. Замыкания позволяют создавать специализированные функциональные преобразования:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| def map_reduce(map_func, reduce_func, initial=None):
def processor(iterable):
# Применяем map_func к каждому элементу
mapped = [map_func(item) for item in iterable]
# Применяем reduce_func для свёртки результатов
if initial is not None:
result = initial
for item in mapped:
result = reduce_func(result, item)
else:
result = mapped[0] if mapped else None
for item in mapped[1:]:
result = reduce_func(result, item)
return result
return processor
# Вычисление суммы квадратов
sum_of_squares = map_reduce(
map_func=lambda x: x**2,
reduce_func=lambda x, y: x + y,
initial=0
)
print(sum_of_squares([1, 2, 3, 4])) # 30 |
|
Производительность замыканий против классов
При выборе между замыканиями и классами разработчики часто задаются вопросом о производительности. Проведем сравнительный анализ:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| import time
# Реализация с использованием замыкания
def counter_closure():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
# Реализация с использованием класса
class CounterClass:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
return self.count
# Тестирование производительности
def test_performance(iterations):
# Тестируем замыкание
closure_counter = counter_closure()
start = time.time()
for _ in range(iterations):
closure_counter()
closure_time = time.time() - start
# Тестируем класс
class_counter = CounterClass()
start = time.time()
for _ in range(iterations):
class_counter.increment()
class_time = time.time() - start
return closure_time, class_time
iterations = 10_000_000
closure_time, class_time = test_performance(iterations)
print(f"Время замыкания: {closure_time:.5f} сек")
print(f"Время класса: {class_time:.5f} сек")
print(f"Соотношение: {closure_time / class_time:.2f}x") |
|
Результаты показывают, что разница в производительности между замыканиями и классами обычно незначительна для большинства практических задач. Выбор между ними должен основываться на других соображениях: удобство использования, читаемость кода, сопровождаемость.
Преимущества замыканий:- Более компактный синтаксис для простых случаев.
- Сильная инкапсуляция за счёт недоступности переменных извне.
- Нет необходимости определять методы, которые не используются.
Преимущества классов:- Более структурированный подход при наличии множества методов.
- Поддержка наследования и полиморфизма.
- Более привычный синтаксис для многих разработчиков.
Комбинирование замыканий с другими функциональными концепциями
Замыкания могут быть объединены с другими концепциями функционального программирования для создания более мощных абстракций.
Каррирование — техника разбиения функции от нескольких аргументов на последовательность функций, каждая от одного аргумента:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| def curry(func):
def curried(*args):
if len(args) >= func.__code__.co_argcount:
return func(*args)
return lambda *more_args: curried(*(args + more_args))
return curried
@curry
def add3(a, b, c):
return a + b + c
# Можно вызывать по-разному
print(add3(1)(2)(3)) # 6
print(add3(1, 2)(3)) # 6
print(add3(1)(2, 3)) # 6 |
|
Частичное применение — фиксация некоторых аргументов функции, создавая новую функцию с меньшим числом аргументов:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
| def partial(func, *fixed_args, **fixed_kwargs):
def wrapper(*args, [B]kwargs):
all_kwargs = {[/B]fixed_kwargs, **kwargs}
return func(*fixed_args, *args, **all_kwargs)
return wrapper
def greet(greeting, name, punctuation='!'):
return f"{greeting}, {name}{punctuation}"
hello = partial(greet, "Hello")
print(hello("World")) # Hello, World!
formal_greeting = partial(greet, punctuation='.')
print(formal_greeting("Good day", "sir")) # Good day, sir. |
|
Комбинирование всех этих концепций позволяет создавать гибкие и мощные функциональные абстракции:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| def pipeline_builder():
transformations = []
def add_step(func):
transformations.append(func)
return add_step
def build():
def pipeline(data):
result = data
for transform in transformations:
result = transform(result)
return result
return pipeline
add_step.build = build
return add_step
# Создаём конвейер обработки данных
process = (pipeline_builder()
(lambda x: [i * 2 for i in x]) # Удваиваем каждый элемент
(lambda x: [i for i in x if i > 5]) # Фильтруем элементы > 5
(lambda x: sum(x)) # Суммируем результаты
.build())
data = [1, 2, 3, 4, 5]
print(process(data)) # 28 (2*4 + 2*5 + 2*3 + 2*5 = 8 + 10 + 6 + 4 = 28) |
|
Этот пример демонстрирует гибкость замыканий при создании сложных функциональных конструкций. Подобные методы широко используются в современных библиотеках функционального программирования для Python, таких как toolz и funcy.
Заключение
Замыкания - мощный инструмент в арсенале Python-разработчика, позволяющий писать элегантный и функциональный код. Однако, как и любая технология, замыкания имеют свои ограничения, которые следует учитывать при проектировании программных решений.
Ограничения и альтернативные подходы
Несмотря на гибкость замыканий, они могут создавать проблемы в сложных системах. Одно из главных ограничений связано с отладкой - замыкания могут затруднять отслеживание состояния программы, поскольку захваченные переменные не видны напрямую при обычном просмотре стека вызовов. Другое ограничение - производительность при работе с большими объемами данных. Хотя разница обычно несущественна, в критичных к скорости приложениях классы иногда работают быстрее, особенно с оптимизациями, доступными для них в CPython.
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
| # Альтернатива замыканию с использованием callable класса
class Counter:
def __init__(self, start=0, step=1):
self.count = start - step
self.step = step
def __call__(self):
self.count += self.step
return self.count
counter = Counter(10)
print(counter()) # 10
print(counter()) # 11 |
|
Когда не стоит использовать замыкания
Замыкания не всегда оптимальный выбор. Вот случаи, когда лучше рассмотреть альтернативы:
1. Когда логика становится сложной и включает множество методов - классы обеспечивают лучшую структуризацию.
2. При необходимости сериализации функциональности - замыкания сложно сериализовать.
3. Когда важна прозрачность кода для команды - замыкания могут быть менее понятны для программистов, привыкших к ООП.
4. При активном использовании наследования и полиморфизма.
Сравнительный анализ замыканий в Python 2 и Python 3
Основное различие в работе с замыканиями между Python 2 и Python 3 связано с ключевым словом nonlocal. В Python 2 изменение переменных из внешней области требовало обходных путей, например, использования изменяемых объектов как контейнеров:
| Python | 1
2
3
4
5
6
7
| # Python 2
def counter():
count = [0] # Изменяемый объект вместо простой переменной
def increment():
count[0] += 1
return count[0]
return increment |
|
Python 3 значительно упростил эту задачу с помощью nonlocal, сделав код чище и выразительнее.
Замыкания в экосистеме Python
Сегодня замыкания прочно вошли в экосистему Python. Они используются во множестве популярных библиотек и фреймворков, особенно в асинхронном программировании с asyncio, веб-фреймворках вроде Flask и Django, и системах обработки данных. Замыкания продолжают эволюционировать вместе с языком, расширяя возможности функционального программирования в Python и предлагая элегантные решения для множества задач. Их понимание и грамотное применение - ценный навык, который поможет писать более выразительный, лаконичный и поддерживаемый код.
Используя замыкания функций, объявите внутреннюю функцию, которая принимает в качестве параметров фамилию и имя 1) Используя замыкания функций, объявите внутреннюю функцию, которая принимает в качестве... Вызовите внутреннюю функцию замыкания и отобразите на экране результат ее работы Используя замыкания функций, объявите внутреннюю функцию, которая принимает два
параметра a , b ,... Вызовите внутреннюю функцию замыкания и отобразите на экране результат ее работы Используя замыкания функций, объявите внутреннюю функцию, которая принимает два
параметра a , b ,... Использование замыкания Используя замыкания функций, объявите внутреннюю функцию, которая принимает в
качестве аргумента... Используя замыкания функций, объявите внутреннюю функцию, которая преобразует строку из списка целых чисел Используя замыкания функций, объявите внутреннюю функцию, которая преобразует строку
из списка... Вызовите внутреннюю функцию замыкания и отобразите на экране результат ее работы Используя замыкания функций, объявите внутреннюю функцию, которая принимает два
параметра a , b ,... Как из Python скрипта выполнить другой python скрипт? Как из Python скрипта выполнить другой python скрипт?
Если он находится в той же папке но нужно... Почему синтаксис Python 2.* и Python 3.* так отличается? Привет!
Решил на досуге заняться изучением Python'a. Читаю книгу по второму питону, а пользуюсь... Что лучше учить Python 2 или Python 3? хочу начать учить питон но полазив в нете, частенько попадалась информация что вроде как 2 будет... Python without python Доброго времени суток!
Хотел узнать, что делать с *.py файлом после того как готова программа,... Python 35 Выполнить файл из python shell Есть файл do.py :
print('start')
import os
import sys
import re
import inspect
def... Python - момент истины. Python - как оружие возмездие против системы Какие модули в python мне нужны для взлома баз данных? Перехвата информации? Внедрения в систему?
...
|