Добрый день, уважаемые студенты! Сегодня мы будем говорить о функциях в Python, одной из самых важных концепций в программировании. Функции позволяют нам организовать код, сделать его более читаемым, модульным и повторно используемым. Давайте начнем с основ.
Функция - это блок кода, который можно вызывать многократно для выполнения определенной задачи. Функции позволяют
абстрагировать детали реализации и сделать код более структурированным. В Python функции объявляются с использованием
ключевого слова def
, за которым следует имя функции и круглые скобки с параметрами. Например:
def greet(name):
print("Привет,", name)
Здесь мы объявили функцию greet
с одним параметром name
. На самом деле параметров может не быть вообще, а может быть
больше одного, рассмотрим эти варианты дальше
Для вызова функции используется имя функции, за которым следуют круглые скобки с передачей аргументов (значений параметров). Например:
greet("Анна")
Этот вызов функции выведет на экран "Привет, Анна".
Вы уже вызывали так называемые built-in
функции, например print
, input
, len
, range
итд. Как вы уже, наверное,
поняли, функции можно писать и самому.
Функции могут возвращать значения с помощью ключевого слова return
. Например:
def add(x, y):
result = x + y
return result
Вызов add(3, 5)
вернет результат сложения 3 и 5, который можно сохранить в переменной или использовать в других
выражениях.
Функции у которых явно не указан
return
будут интерпретироваться питоном как функция в которой последней инструкцией написаноreturn None
, потому что у функции всегда должно быть возвращаемое значение.
def count_sum(a, b):
return a + b
def print_var(a):
print(a)
res = count_sum(5, 10) # Значение будет 15
res2 = print_var(10) # Значение будет None, потому что функция ничего не возвращает
Переменные, объявленные внутри функции, называются локальными и видны только внутри этой функции. Попробуем это продемонстрировать на примере:
def multiply(a, b):
result = a * b
return result
c = 2
d = 3
product = multiply(c, d)
print(result) # Ошибка! Переменная result не видна за пределами функции
В этом примере переменная result
видна только внутри функции multiply
.
Python позволяет указывать значения по умолчанию для аргументов функции. Это позволяет вызывать функцию с меньшим количеством аргументов, если значения по умолчанию заданы. Например:
def power(base, exponent=2):
result = base ** exponent
return result
print(power(3)) # Выведет 9, так как exponent по умолчанию равен 2
print(power(2, 3)) # Выведет 8, так как мы явно указали значение exponent
Обратите внимание, аргументы со значением по умолчанию всегда должны быть указаны после аргументов без такого значения! Почему так, рассмотрим ниже.
# ОШИБКА, ТАК СДЕЛАТЬ НЕЛЬЗЯ
def power(base=5, exponent):
result = base ** exponent
return result
Python - это язык с динамической типизацией, что означает, что типы переменных определяются автоматически во время выполнения программы. Однако, начиная с версии Python 3.5, можно использовать аннотации типов для объявления ожидаемых типов аргументов и возвращаемых значений функции. Это делает код более читаемым и помогает IDE и инструментам статического анализа проводить проверку типов. Например:
def add(x: int, y: int) -> int: # Стрелка это два символа "-" и ">"
result = x + y
return result
Типы данных указывать в python не обязательно, но в современном мире, код без типизаций считается моветоном, на серьезных проектах все и всегда будет покрыто типизациями.
Здесь мы аннотировали аргументы x
и y
как int
, а возвращаемое значение как int
.
Через двоеточие указывается тип данных для каждого передаваемого аргумента, а за скобками через стрелку, указывается какой тип данных возвращает наша функция.
В Python вы можете передавать функциям аргументы, количество которых может варьироваться. Для этого используются два специальных синтаксиса:
-
*args
: Этот синтаксис позволяет передавать произвольное количество аргументов в виде кортежа (tuple). Имяargs
является соглашением (хоть матом напишите, работать будет, но принято использовать именно это слово). -
**kwargs
: Этот синтаксис позволяет передавать произвольное количество именованных аргументов в виде словаря ( dictionary). Имяkwargs
также является соглашением, но можно использовать любое имя после**
.
args
используется для передачи не именованных параметров, аkwargs
для передачи именованных
def print_args(*args):
for arg in args:
print(arg)
print_args(1, 2, 3, "hello") # Выведет все переданные аргументы
В этом примере *args
собирает все переданные аргументы в кортеж args
, который затем можно перебрать в цикле.
def print_kwargs(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_kwargs(name="John", age=25, city="New York") # Выведет все переданные именованные аргументы
Здесь **kwargs
собирает все переданные именованные аргументы в словарь kwargs
, который можно перебрать в цикле.
Вы также можете комбинировать *args
и **kwargs
в одной функции, но *args
должен идти перед **kwargs
:
def print_all_args_and_kwargs(arg1, *args, kwarg1="default", **kwargs):
print("Обязательный аргумент:", arg1)
print("Дополнительные аргументы (*args):", args)
print("Именованный аргумент (kwarg1):", kwarg1)
print("Дополнительные именованные аргументы (**kwargs):", kwargs)
print_all_args_and_kwargs("first", "second", "third", kwarg1="custom", key1="value1", key2="value2")
В этом примере функция print_all_args_and_kwargs
принимает один обязательный аргумент, произвольное количество
аргументов *args
, один именованный аргумент kwarg1
со значением по умолчанию, и произвольное количество именованных
аргументов **kwargs
. Это позволяет гибко работать с разными видами аргументов при вызове функции.
Использование *args
и **kwargs
может быть полезным, когда вам нужно создавать более гибкие функции, способные
обрабатывать разное количество и типы аргументов.
На самом деле звездочки могут быть использованы не только для определения аргументов функции.
Оператор *
позволяет распаковать элементы кортежа или списка и передать их как отдельные аргументы функции. Это
полезно, когда у вас есть кортеж (или список) с переменным количеством элементов, и вы хотите передать их в функцию,
которая ожидает отдельные аргументы.
def multiply(a:int, b:int) -> int:
return a * b
values = (2, 3)
result = multiply(*values) # Распаковываем кортеж и передаем его элементы как аргументы функции
print(result) # Выведет 6, так как 2 * 3 = 6
В этом примере мы объявили функцию multiply
, которая принимает два аргумента. Затем мы создали кортеж values
с двумя
элементами и использовали оператор *
для распаковки кортежа и передачи его элементов как аргументы функции multiply
.
Или можно сделать так:
a, b, *c = 1, 2, 3, 4, 5
print(a, b, c) # Распечатает 1, 2, [3, 4, 5], первые два аргумента будут переданы напрямую, а все остальные будут распакованы как список
def add(a: int, b: int, c: int) -> int:
return a + b + c
numbers = [1, 2, 3]
result = add(*numbers) # Распаковываем список и передаем его элементы как аргументы функции
print(result) # Выведет 6, так как 1 + 2 + 3 = 6
В этом примере мы используем список numbers
и также распаковываем его элементы как аргументы функции add
.
Распаковка с использованием *
может быть полезной, когда вам нужно передать переменное количество аргументов функции
или когда вы работаете с данными, хранящимися в кортежах или списках. Это делает ваш код более гибким и читаемым.
Давайте добавим информацию о распаковке словарей с использованием оператора двойной звездочки **
в Python.
Оператор **
позволяет распаковать словарь и передать его элементы как именованные аргументы функции. Это полезно,
когда у вас есть словарь с переменным количеством ключей и значениями, и вы хотите передать их в функцию, которая
ожидает именованные аргументы.
def print_person_info(name: str, age: int) -> None:
print(f"Имя: {name}, Возраст: {age}")
person_info = {"name": "John", "age": 30}
print_person_info(**person_info) # Распаковываем словарь и передаем его элементы как именованные аргументы функции
В этом примере мы объявили функцию print_person_info
, которая принимает два именованных аргумента (name
и age
).
Затем мы создали словарь person_info
с ключами "name"
и "age"
и их соответствующими значениями. С помощью
оператора **
мы распаковываем словарь и передаем его элементы как именованные аргументы функции print_person_info
.
Вы также можете комбинировать *args
и **kwargs
в одной функции, чтобы обработать как позиционные, так и именованные
аргументы.
def print_info(*args, **kwargs):
for arg in args:
print(arg)
for key, value in kwargs.items():
print(f"{key}: {value}")
values = (1, 2, 3)
info = {"name": "John", "age": 30}
print_info(*values, **info) # Распаковываем кортеж и словарь и передаем их элементы как аргументы функции
В этом примере функция print_info
принимает как позиционные аргументы, так и именованные аргументы, используя *args
и **kwargs
.
Распаковка с использованием **
может быть полезной, когда вам нужно передавать переменное количество именованных
аргументов функции или когда вы работаете с данными, хранящимися в словарях. Это делает ваш код более гибким и удобным
для работы с разными видами данных.
Во всех задачах необходимо указывать типизации!
-
Простое сложение. Напишите функцию add_numbers, которая принимает два целых числа и возвращает их сумму.
-
Приветствие. Напишите функцию greet, которая принимает строку name и возвращает приветственное сообщение.
-
Факториал числа. Напишите функцию factorial, которая принимает целое число и возвращает его факториал.
-
Среднее значение. Напишите функцию average, которая принимает произвольное количество чисел и возвращает их среднее значение. Подумайте какие там будут типы данных
-
Форматирование строки. Напишите функцию format_string, которая принимает строковый шаблон и произвольное количество именованных аргументов для подстановки в шаблон. Например
format_string("some {value1}, another {value2}", value1="test", value2="something_else")
-
Объединение словарей. Напишите функцию merge_dicts, которая принимает произвольное количество словарей и объединяет их в один.
-
Четные и нечетные числа. Напишите функцию even_odd, которая принимает произвольное количество целых чисел и возвращает кортеж из двух списков: один с четными числами, другой с нечетными. Продумайте типизацию
-
Фильтрация списка. Напишите функцию filter_list которая принимает список целых чисел и пороговое значение, и возвращает новый список с числами из оригинального списка, которые больше порога. Продумайте типизацию.
-
Калькулятор. Напишите функцию calculator, которая принимает два числа и строку, представляющую арифметическую операцию ('add', 'subtract', 'multiply', 'divide'), и возвращает результат этой операции. Продумайте типизацию. Например
calculator(4,5,"multiply")
вернет20
.
Теперь становится очень важно помнить какие типы данных являются изменяемыми, а какие не изменяемыми из базовых типов! Не изменяемые: Строка(String), Число(Number), Кортеж(Tuple). Изменяемые: Список(List), Множество(Set), Словарь(Dict).
В Python существуют два типа данных: изменяемые (mutable) и неизменяемые (immutable). Примерами изменяемых типов данных являются списки (list) и словари (dict), а неизменяемых - целые числа (int), строки (str) и кортежи (tuple).
И бывает два вида передачи данных в функцию (и не только) по ссылке
и по значению
.
Передача по ссылке предполагает, что вы передаете не сами данные, а только адрес нахождения этого объекта в памяти!
Передача по значению предполагает, что вы передаете копию переменной!
При передаче изменяемых типов данных в функцию важно понимать, что функция может изменить сам объект, который был передан в качестве аргумента. Это происходит потому, что изменяемые объекты передаются по ссылке, а не по значению.
Рассмотрим пример:
def modify_list(my_list: list) -> list:
my_list.append(4)
original_list = [1, 2, 3]
modify_list(original_list)
print(original_list) # Выведет [1, 2, 3, 4]
В этом примере мы передали список original_list
в функцию modify_list
, и функция добавила элемент 4 в этот список.
После вызова функции original_list
был изменен и теперь содержит элемент 4.
Чтобы избежать таких побочных эффектов, можно передавать изменяемые объекты в функции с помощью копии объекта или
использовать методы копирования, например, copy.copy()
или copy.deepcopy()
из модуля copy
.
import copy
def modify_list_safely(my_list: list) -> list:
new_list = copy.copy(my_list)
new_list.append(4)
return new_list
original_list = [1, 2, 3]
modified_list = modify_list_safely(original_list)
print(original_list) # Выведет [1, 2, 3]
print(modified_list) # Выведет [1, 2, 3, 4]
Таким образом, при работе с изменяемыми объектами важно быть осторожными и учитывать, как изменения в функции могут повлиять на оригинальные объекты.
Модуль copy
предоставляет функцию copy.copy()
, которая позволяет создавать поверхностные копии объектов. Это
означает, что она создает новый объект, который является копией оригинала, но не рекурсивно копирует все вложенные
объекты. Вложенные объекты по-прежнему будут ссылаться на одни и те же данные.
import copy
original_list = [1, 2, [3, 4]]
copied_list = copy.copy(original_list)
print(original_list) # Выведет [1, 2, [3, 4]]
print(copied_list) # Выведет [1, 2, [3, 4]]
# Изменим вложенный список в копии
copied_list[2][0] = 99
print(original_list) # Выведет [1, 2, [99, 4]]
print(copied_list) # Выведет [1, 2, [99, 4]]
Как видно из примера, изменение вложенного списка в копии также затрагивает оригинал. Это происходит потому, что копия создается только на верхнем уровне, а вложенные объекты остаются общими для оригинала и копии.
Для создания глубоких копий объектов, включая все вложенные объекты, используйте функцию copy.deepcopy()
. Глубокая
копия создает новую структуру данных, которая полностью независима от оригинала.
import copy
original_list = [1, 2, [3, 4]]
deep_copied_list = copy.deepcopy(original_list)
print(original_list) # Выведет [1, 2, [3, 4]]
print(deep_copied_list) # Выведет [1, 2, [3, 4]]
# Изменим вложенный список в глубокой копии
deep_copied_list[2][0] = 99
print(original_list) # Выведет [1, 2, [3, 4]]
print(deep_copied_list) # Выведет [1, 2, [99, 4]]
Как видно из примера, изменения во вложенном списке в глубокой копии не влияют на оригинальный список. Это позволяет безопасно работать с вложенными объектами и избегать неожиданных изменений в оригинальных данных.
Итак, функции copy.copy()
и copy.deepcopy()
в модуле copy
предоставляют удобные средства для копирования объектов
с учетом их изменяемости и вложенности. Выбор между ними зависит от вашего конкретного случая использования.
Функция map
используется для применения определенной функции к каждому элементу в итерируемой последовательности (
например, списку) и создания новой последовательности с результатами. Это позволяет применять одну функцию к нескольким
элементам без явного использования циклов. Пример:
def square(x: int) -> int:
return x ** 2
numbers = [1, 2, 3, 4, 5]
squared_numbers = list(map(square, numbers))
print(squared_numbers) # Выведет [1, 4, 9, 16, 25]
В этом примере мы создали функцию square
, которая возводит число в квадрат, и применили её ко всем элементам
списка numbers
с помощью map
.
Функция zip
позволяет объединить несколько итерируемых последовательностей в одну последовательность кортежей.
Количество элементов в результирующей последовательности равно минимальному количеству элементов среди всех переданных
последовательностей. Пример:
names = ["Анна", "Иван", "Мария"]
scores = [90, 85, 88]
zipped_data = list(zip(names, scores))
print(zipped_data) # Выведет [('Анна', 90), ('Иван', 85), ('Мария', 88)]
Здесь мы объединили список имен и список оценок в список кортежей, создавая пары "имя - оценка".
Функция filter
используется для фильтрации элементов в итерируемой последовательности на основе заданного условия (
функции). Она возвращает только те элементы, для которых условие истинно. Пример:
def is_even(x: int) -> bool:
return x % 2 == 0
numbers = [1, 2, 3, 4, 5, 6]
even_numbers = list(filter(is_even, numbers))
print(even_numbers) # Выведет [2, 4, 6]
Здесь мы определили функцию is_even
, которая проверяет, является ли число четным, и использовали filter
, чтобы
отфильтровать только четные числа из списка numbers
.
Использование функций map
, zip
, filter
делает код более читаемым и позволяет выполнять разнообразные операции с
данными в более функциональном стиле.
Лямбда-функции (или анонимные функции) - это специальный вид функций, которые могут быть определены в одной строке без
использования ключевого слова def
. Они часто используются для создания коротких функций, которые передаются в качестве
аргументов другим функциям. Например:
square = lambda x: x ** 2
print(square(5)) # Выведет 25
Лямбда-функции полезны, когда требуется передать небольшую функцию в функцию высшего порядка, такую как map
, filter
или sorted
.
Те же примеры для map
и filter
в реальности выглядели бы вот так:
map:
numbers = [1, 2, 3, 4, 5]
squared_numbers = list(map(lambda x: x**2, numbers))
print(squared_numbers) # Выведет [1, 4, 9, 16, 25]
filter:
numbers = [1, 2, 3, 4, 5, 6]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers) # Выведет [2, 4, 6]
-
Применение функции ко всем элементам списка. Используя map и лямбда-функцию, напишите код, который принимает список целых чисел и возвращает список их квадратов.
-
Фильтрация нечетных чисел. Используя filter и лямбда-функцию, напишите код, который принимает список целых чисел и возвращает список только с нечетными числами.
-
Суммирование элементов списков. Используя zip и лямбда-функцию, напишите код, который принимает два списка одинаковой длины и возвращает список, где каждый элемент - это сумма элементов из входных списков на соответствующих позициях.
list1 = [1, 2, 3]
list2 = [4, 5, 6]
result = [5, 7, 9]
- Преобразование строк в числа. Используя map и лямбда-функцию, напишите код, который принимает список строковых представлений чисел и возвращает список этих чисел в виде целых чисел.
string_numbers = ["1", "2", "3", "4", "5"]
result = [1, 2, 3, 4, 5]
- Объединение списков словарей. Используя zip, напишите код, который принимает два списка словарей и возвращает список словарей, где каждый словарь - это объединение словарей из входных списков на соответствующих позициях.
list1 = [{'a': 1}, {'b': 2}]
list2 = [{'c': 3}, {'d': 4}]
result = [{'a': 1, 'c': 3}, {'b': 2, 'd': 4}]
-
Фильтрация строк по длине. Используя filter и лямбда-функцию, напишите код, который принимает список строк и возвращает только те строки, длина которых больше 3 символов.
-
Вычисление длины строк Используя map и лямбда-функцию, напишите код, который принимает список строк и возвращает список их длин.