Skip to content
This repository was archived by the owner on Jul 2, 2024. It is now read-only.

Files

Latest commit

author
Vlad Ponomarov
Nov 26, 2023
a528c76 · Nov 26, 2023

History

History
512 lines (341 loc) · 29.9 KB

lesson7.md

File metadata and controls

512 lines (341 loc) · 29.9 KB

Урок 6. Функции, типизация, lambda. Map, zip, filter, reduce.

Добрый день, уважаемые студенты! Сегодня мы будем говорить о функциях в Python, одной из самых важных концепций в программировании. Функции позволяют нам организовать код, сделать его более читаемым, модульным и повторно используемым. Давайте начнем с основ.

Что такое функция?

Функция - это блок кода, который можно вызывать многократно для выполнения определенной задачи. Функции позволяют абстрагировать детали реализации и сделать код более структурированным. В Python функции объявляются с использованием ключевого слова def, за которым следует имя функции и круглые скобки с параметрами. Например:

def greet(name):
    print("Привет,", name)

Здесь мы объявили функцию greet с одним параметром name.

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

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

greet("Анна")

Этот вызов функции выведет на экран "Привет, Анна".

Возвращение значений

Функции могут возвращать значения с помощью ключевого слова return. Например:

def add(x, y):
    result = x + y
    return result

Вызов add(3, 5) вернет результат сложения 3 и 5, который можно сохранить в переменной или использовать в других выражениях.

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

Область видимости (scope) переменных

Переменные, объявленные внутри функции, называются локальными и видны только внутри этой функции. Попробуем это продемонстрировать на примере:

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

Типизация и аннотации

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

def add(x: int, y: int) -> int:
    result = x + y
    return result

Здесь мы аннотировали аргументы x и y как int, а возвращаемое значение как int.

Давайте добавим информацию о передаче функциям случайного количества параметров с использованием *args и **kwargs.

Передача случайного количества параметров

В Python вы можете передавать функциям аргументы, количество которых может варьироваться. Для этого используются два специальных синтаксиса:

  1. *args: Этот синтаксис позволяет передавать произвольное количество аргументов в виде кортежа (tuple). Имя args является соглашением, но вы можете использовать любое имя после *.

  2. **kwargs: Этот синтаксис позволяет передавать произвольное количество именованных аргументов в виде словаря ( dictionary). Имя kwargs также является соглашением, но можно использовать любое имя после **.

Пример с *args

def print_args(*args):
    for arg in args:
        print(arg)


print_args(1, 2, 3, "hello")  # Выведет все переданные аргументы

В этом примере *args собирает все переданные аргументы в кортеж args, который затем можно перебрать в цикле.

Пример с **kwargs

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 может быть полезным, когда вам нужно создавать более гибкие функции, способные обрабатывать разное количество и типы аргументов.

Давайте добавим информацию о распаковке кортежей с использованием оператора звездочки * в Python.

Распаковка кортежей с использованием *

Оператор * позволяет распаковать элементы кортежа или списка и передать их как отдельные аргументы функции. Это полезно, когда у вас есть кортеж (или список) с переменным количеством элементов, и вы хотите передать их в функцию, которая ожидает отдельные аргументы.

Пример распаковки кортежа

def multiply(a, b):
    return a * b


values = (2, 3)
result = multiply(*values)  # Распаковываем кортеж и передаем его элементы как аргументы функции
print(result)  # Выведет 6, так как 2 * 3 = 6

В этом примере мы объявили функцию multiply, которая принимает два аргумента. Затем мы создали кортеж values с двумя элементами и использовали оператор * для распаковки кортежа и передачи его элементов как аргументы функции multiply.

Пример распаковки списка

def add(a, b, c):
    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, age):
    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.

Распаковка с использованием ** может быть полезной, когда вам нужно передавать переменное количество именованных аргументов функции или когда вы работаете с данными, хранящимися в словарях. Это делает ваш код более гибким и удобным для работы с разными видами данных.

Лямбда-функции

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

square = lambda x: x ** 2
print(square(5))  # Выведет 25

Лямбда-функции полезны, когда требуется передать небольшую функцию в функцию высшего порядка, такую как map, filter или sorted.

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

Передача изменяемых типов данных

В Python существуют два типа данных: изменяемые (mutable) и неизменяемые (immutable). Примерами изменяемых типов данных являются списки (list) и словари (dict), а неизменяемых - целые числа (int), строки (str) и кортежи (tuple).

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

Рассмотрим пример:

def modify_list(my_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):
    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]

Таким образом, при работе с изменяемыми объектами важно быть осторожными и учитывать, как изменения в функции могут повлиять на оригинальные объекты.

Для работы с изменяемыми объектами и избежания неожиданных побочных эффектов в Python можно использовать функции copy и deepcopy из модуля copy. Давайте рассмотрим их подробнее.

Модуль copy и функция copy

Модуль 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]]

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

Функция deepcopy

Для создания глубоких копий объектов, включая все вложенные объекты, используйте функцию 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, zip, filter и reduce в Python для манипуляции данными и работы с последовательностями.

Функция map

Функция map используется для применения определенной функции к каждому элементу в итерируемой последовательности ( например, списку) и создания новой последовательности с результатами. Это позволяет применять одну функцию к нескольким элементам без явного использования циклов. Пример:

def square(x):
    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

Функция zip позволяет объединить несколько итерируемых последовательностей в одну последовательность кортежей. Количество элементов в результирующей последовательности равно минимальному количеству элементов среди всех переданных последовательностей. Пример:

names = ["Анна", "Иван", "Мария"]
scores = [90, 85, 88]

zipped_data = list(zip(names, scores))
print(zipped_data)  # Выведет [('Анна', 90), ('Иван', 85), ('Мария', 88)]

Здесь мы объединили список имен и список оценок в список кортежей, создавая пары "имя - оценка".

Функция filter

Функция filter используется для фильтрации элементов в итерируемой последовательности на основе заданного условия ( функции). Она возвращает только те элементы, для которых условие истинно. Пример:

def is_even(x):
    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 делает код более читаемым и позволяет выполнять разнообразные операции с данными в более функциональном стиле.

Конечно, давайте добавим информацию о рекурсии в лекцию.

Рекурсия

Рекурсия - это концепция в программировании, при которой функция вызывает саму себя внутри своего тела. Это мощный инструмент, который позволяет решать задачи, которые могут быть разбиты на более мелкие подзадачи того же типа. Рекурсия в Python работает аналогично рекурсии в математике.

Основные элементы рекурсии

  1. Базовый случай (Base Case): Это условие, при котором рекурсия завершается и функция больше не вызывает саму себя. Без базового случая рекурсивная функция будет вызывать себя бесконечно.

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

Пример: Вычисление факториала с использованием рекурсии

Давайте рассмотрим пример рекурсивной функции для вычисления факториала числа. Факториал числа n (обозначается как n!) - это произведение всех положительных целых чисел от 1 до n.

def factorial(n):
    # Базовый случай: факториал 0 или 1 равен 1
    if n == 0 or n == 1:
        return 1
    # Рекурсивный случай: вычисляем факториал для (n-1) и умножаем на n
    else:
        return n * factorial(n - 1)


# Вызываем функцию для вычисления факториала числа 5
result = factorial(5)
print(result)  # Выведет 120, так как 5! = 5 * 4 * 3 * 2 * 1 = 120

Преимущества и ограничения рекурсии

Преимущества:

  • Рекурсия может сделать код более читаемым и интуитивно понятным, особенно для задач, связанных с древовидными или рекурсивными структурами данных.
  • Она может предоставить более лаконичное и элегантное решение для некоторых задач.

Ограничения:

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

При использовании рекурсии важно правильно формулировать базовый и рекурсивный случаи, чтобы функция завершилась и не вошла в бесконечный цикл.

Задачи

Задача 1: Сумма чисел

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

Задача 2: Расчет среднего значения

Напишите функцию calculate_average, которая принимает список чисел в качестве аргумента и возвращает среднее значение этих чисел. Затем вызовите функцию с разными списками чисел.

Задача 3: Факториал числа

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

Задача 4: Фильтрация списка

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

Задача 5: Палиндром

Напишите функцию is_palindrome, которая принимает строку в качестве аргумента и возвращает True, если строка является палиндромом (читается одинаково слева направо и справа налево), и False в противном случае. Проверьте функцию на различных строках.

Задача 6: Работа со списками

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

Задача 7: Фибоначчи

Напишите функцию fibonacci, которая принимает число n в качестве аргумента и возвращает список первых n чисел Фибоначчи. Проверьте функцию для разных значений n.