Skip to content

Модуль 3. Работа с SQLite в Python ​

Цели модуля ​

  • Научиться подключать Python-программу к SQLite-базе данных.
  • Понять полный цикл работы с базой данных из Python.
  • Научиться выполнять базовые операции CRUD из Python-кода.
  • Перейти от отдельных SQL-команд к полноценной программе с базой данных.
  • Подготовиться к теме обработки ошибок при работе с БД.

1. Полноценная интеграция Python и SQLite ​

1.1 Теория ​

Что значит интеграция Python и SQLite

На прошлом модуле база данных рассматривалась отдельно: таблицы создавались через SQLiteStudio, а SQL-запросы выполнялись вручную.

На этом этапе база данных становится частью Python-программы. Это значит, что программа должна уметь:

  • подключаться к файлу базы данных;
  • создавать таблицу, если она ещё не существует;
  • добавлять записи;
  • читать записи;
  • изменять записи;
  • удалять записи.

Именно это и называется интеграцией Python с SQLite.

Общий порядок работы с базой данных в Python

При работе с SQLite из Python обычно используется одна и та же последовательность действий:

  1. Подключиться к базе данных.
  2. Создать курсор.
  3. Выполнить SQL-запрос.
  4. Сохранить изменения, если данные были изменены.
  5. Получить результат, если запрос возвращает данные.
  6. Закрыть соединение.

Краткая схема:

подключение -> запрос -> сохранение -> чтение -> закрытие

Модуль sqlite3

В Python есть встроенный модуль sqlite3 для работы с SQLite. Его не нужно устанавливать отдельно.

python
import sqlite3

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

Соединение и курсор

Команда sqlite3.connect('school.db') создаёт соединение с файлом базы данных.

Соединение нужно для работы программы с базой данных.

После этого создаётся курсор:

python
cursor = conn.cursor()

Курсор используется для выполнения SQL-запросов.

То есть:

  • conn — соединение с базой данных;
  • cursor — объект для выполнения запросов.

Пример: создание таблицы из Python

python
import sqlite3

conn = sqlite3.connect('school.db')
cursor = conn.cursor()

cursor.execute('''
CREATE TABLE IF NOT EXISTS students (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    age INTEGER
)
''')

conn.commit()
conn.close()

Разбор по шагам

  1. import sqlite3 — импортируем модуль для работы с SQLite.
  2. sqlite3.connect('school.db') — подключаемся к базе school.db.
  3. Если файла базы ещё нет, SQLite создаст его автоматически.
  4. conn.cursor() — создаём курсор.
  5. cursor.execute(...) — выполняем SQL-запрос.
  6. CREATE TABLE IF NOT EXISTS students — создаём таблицу students, если её ещё нет.
  7. conn.commit() — сохраняем изменения.
  8. conn.close() — закрываем соединение.

Зачем нужен commit()

Если запрос изменяет базу данных, изменения нужно сохранить через commit().

python
conn.commit()

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

Обычно commit() нужен после:

  • CREATE TABLE;
  • INSERT;
  • UPDATE;
  • DELETE.

После SELECT он обычно не нужен, потому что SELECT только читает данные.

Полный учебный пример с CRUD

Следующий пример показывает уже не один запрос, а небольшую рабочую программу.

Она:

  • создаёт таблицу;
  • добавляет записи;
  • выводит записи;
  • изменяет запись;
  • удаляет запись.
python
import sqlite3

DB_NAME = 'school.db'


def init_db():
    conn = sqlite3.connect(DB_NAME)
    cursor = conn.cursor()

    cursor.execute('''
    CREATE TABLE IF NOT EXISTS students (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        age INTEGER
    )
    ''')

    conn.commit()
    conn.close()


def add_student(name, age):
    conn = sqlite3.connect(DB_NAME)
    cursor = conn.cursor()

    cursor.execute(
        'INSERT INTO students (name, age) VALUES (?, ?)',
        (name, age)
    )

    conn.commit()
    conn.close()


def show_students():
    conn = sqlite3.connect(DB_NAME)
    cursor = conn.cursor()

    cursor.execute('SELECT id, name, age FROM students')
    rows = cursor.fetchall()

    print('Список студентов:')
    for row in rows:
        print(row)

    conn.close()


def update_student_age(student_id, new_age):
    conn = sqlite3.connect(DB_NAME)
    cursor = conn.cursor()

    cursor.execute(
        'UPDATE students SET age = ? WHERE id = ?',
        (new_age, student_id)
    )

    conn.commit()
    conn.close()


def delete_student(student_id):
    conn = sqlite3.connect(DB_NAME)
    cursor = conn.cursor()

    cursor.execute(
        'DELETE FROM students WHERE id = ?',
        (student_id,)
    )

    conn.commit()
    conn.close()


init_db()
add_student('Анна', 19)
add_student('Иван', 20)
show_students()
update_student_age(1, 21)
show_students()
delete_student(2)
show_students()

Что показывает этот пример

В этом примере каждая функция выполняет отдельную задачу:

  • init_db() — создаёт таблицу;
  • add_student() — добавляет запись;
  • show_students() — выводит записи;
  • update_student_age() — изменяет запись;
  • delete_student() — удаляет запись.

Такой подход удобнее, чем писать все действия подряд в одном большом блоке кода.

Почему используются ?, а не f-строки

В запросах значения передаются так:

python
cursor.execute(
    'INSERT INTO students (name, age) VALUES (?, ?)',
    (name, age)
)

Такой способ лучше, потому что:

  1. он безопаснее;
  2. он уменьшает количество ошибок с кавычками;
  3. Python сам корректно передаёт значения;
  4. это стандартный способ передачи параметров в SQL-запрос.

Как разбирать Python-код с базой данных

При чтении кода удобно задавать себе такие вопросы:

  1. Где создаётся соединение?
  2. Где создаётся курсор?
  3. Какой SQL-запрос выполняется?
  4. Есть ли commit()?
  5. Есть ли чтение результата через fetchone() или fetchall()?
  6. Где закрывается соединение?

Частые ошибки

  • Забывают commit() после INSERT, UPDATE или DELETE.
  • Закрывают соединение слишком рано.
  • Путают conn и cursor.
  • Ожидают, что SELECT сам выведет данные на экран.
  • Подставляют значения в SQL через f-строки.
  • Считают, что база изменится без commit().

Что нужно запомнить после этого раздела

Работа с SQLite из Python строится по одному и тому же принципу:

  • открыть соединение;
  • выполнить запрос;
  • при необходимости сохранить изменения;
  • получить результат;
  • закрыть соединение.

1.2 Практика ​

Задание 1. Первое подключение к базе данных

Цель - написать скрипт, который подключается к базе и создаёт таблицу.

Шаги:

  1. Создайте файл lesson5_task1.py.
  2. Подключитесь к базе school.db.
  3. Создайте таблицу students, если её ещё нет.
  4. Сохраните изменения.
  5. Закройте соединение.

Ожидаемый результат: после запуска скрипта в базе school.db существует таблица students.

Задание 2. Добавление и чтение данных

Цель - научиться добавлять записи и выводить их на экран.

Шаги:

  1. Подключитесь к school.db.
  2. Добавьте в таблицу двух студентов.
  3. Выполните SELECT id, name, age FROM students.
  4. Получите данные через fetchall().
  5. Выведите все записи в цикле for.
  6. Закройте соединение.

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

Задание 3. Мини-CRUD

Цель - собрать в одном файле полный рабочий код для работы с таблицей.

Шаги:

  1. Создайте функции add_student, show_students, update_student_age, delete_student.
  2. Вызовите их по очереди внизу файла.
  3. После каждого шага проверяйте, как меняется таблица.
  4. Сравните результат в Python и в SQLiteStudio.

Ожидаемый результат: в файле реализован полный цикл работы с базой данных из Python.


2. Обработка ошибок при работе с SQLite ​

2.1 Теория ​

Зачем обрабатывать ошибки

При работе с базой данных программа может столкнуться с ошибками. Например, файл базы данных может быть недоступен, SQL-запрос может быть написан неправильно, таблица может не существовать, а пользователь может ввести данные в неверном формате.

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

Обработка ошибок нужна для того, чтобы программа:

  • не завершалась аварийно;
  • показывала понятное сообщение;
  • закрывала соединение с базой данных;
  • не оставляла данные в неопределённом состоянии.

Конструкция try-except

В Python ошибки обрабатываются через try-except.

python
try:
    number = int(input('Введите возраст: '))
except ValueError:
    print('Возраст должен быть числом')

Разбор:

  1. В блок try помещается код, в котором может возникнуть ошибка.
  2. Если ошибки нет, код выполняется как обычно.
  3. Если возникает ValueError, программа переходит в блок except.
  4. Вместо аварийного завершения выводится понятное сообщение.

Ошибки SQLite

Для ошибок SQLite используется sqlite3.Error. Это общий тип ошибок, связанных с базой данных.

Пример:

python
import sqlite3

try:
    conn = sqlite3.connect('school.db')
    cursor = conn.cursor()
    cursor.execute('SELECT * FROM unknown_table')
except sqlite3.Error as error:
    print('Ошибка при работе с базой данных:', error)
finally:
    conn.close()

Разбор:

  1. Программа подключается к базе school.db.
  2. Затем пытается выполнить запрос к таблице unknown_table.
  3. Если такой таблицы нет, SQLite создаст ошибку.
  4. Ошибка попадёт в блок except sqlite3.Error.
  5. В блоке finally соединение закрывается.

В этом примере есть важный недостаток: если ошибка произойдёт до создания conn, строка conn.close() тоже вызовет ошибку. Поэтому безопаснее заранее создать переменную conn со значением None.

Более безопасный вариант

python
import sqlite3

conn = None

try:
    conn = sqlite3.connect('school.db')
    cursor = conn.cursor()
    cursor.execute('SELECT * FROM students')
    rows = cursor.fetchall()

    for row in rows:
        print(row)
except sqlite3.Error as error:
    print('Ошибка при работе с базой данных:', error)
finally:
    if conn is not None:
        conn.close()

Разбор:

  1. conn = None создаёт переменную заранее.
  2. Если подключение прошло успешно, в conn будет соединение с базой.
  3. Если при работе с базой возникнет ошибка, она попадёт в except.
  4. В finally проверяется, было ли соединение создано.
  5. Если соединение есть, оно закрывается.

Обработка ошибок ввода

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

Например, возраст должен быть числом:

python
try:
    age = int(input('Введите возраст: '))
except ValueError:
    print('Возраст должен быть целым числом')

Если пользователь введёт текст вместо числа, программа не упадёт, а покажет понятное сообщение.

Рабочий пример: добавление студента с обработкой ошибок

python
import sqlite3

DB_NAME = 'school.db'


def add_student(name, age):
    conn = None

    try:
        conn = sqlite3.connect(DB_NAME)
        cursor = conn.cursor()

        cursor.execute(
            'INSERT INTO students (name, age) VALUES (?, ?)',
            (name, age)
        )

        conn.commit()
        print('Студент добавлен')
    except sqlite3.Error as error:
        print('Ошибка при добавлении студента:', error)
    finally:
        if conn is not None:
            conn.close()


try:
    name = input('Введите имя: ')
    age = int(input('Введите возраст: '))
    add_student(name, age)
except ValueError:
    print('Возраст должен быть числом')

Разбор:

  1. Внешний try-except проверяет ввод возраста.
  2. Если возраст введён неправильно, сработает except ValueError.
  3. Функция add_student() отвечает за работу с базой.
  4. Внутри функции обрабатываются ошибки SQLite.
  5. finally закрывает соединение с базой данных.

Что не стоит делать

Не стоит писать пустой except:

python
try:
    cursor.execute('SELECT * FROM students')
except:
    pass

Такой код скрывает ошибку. Программа не сообщает, что произошло, и отладка становится сложнее.

Лучше указывать конкретный тип ошибки:

python
except sqlite3.Error as error:
    print('Ошибка базы данных:', error)

Частые ошибки

  • Обрабатывать все ошибки через пустой except.
  • Не закрывать соединение с базой данных.
  • Забывать commit() после изменения данных.
  • Не проверять данные, введённые пользователем.
  • Смешивать в одном try слишком много разных действий.

2.2 Практика ​

Задание 1. Безопасный ввод возраста

Цель - обработать ошибку при вводе числа.

Шаги:

  1. Создайте файл lesson5_task4.py.
  2. Запросите у пользователя имя и возраст.
  3. Преобразуйте возраст в int.
  4. Если возраст введён неверно, выведите сообщение Возраст должен быть числом.
  5. Проверьте программу на корректном и некорректном вводе.

Ожидаемый результат: программа не завершается с ошибкой при вводе текста вместо возраста.

Задание 2. Чтение из базы с обработкой ошибок

Цель - обработать ошибку SQLite при выполнении запроса.

Шаги:

  1. Подключитесь к базе school.db.
  2. Выполните запрос SELECT id, name, age FROM students.
  3. Выведите найденные записи.
  4. Добавьте обработку sqlite3.Error.
  5. Проверьте обработку ошибки, временно изменив имя таблицы на неправильное.

Ожидаемый результат: при ошибке программа выводит понятное сообщение и закрывает соединение.

Задание 3. Улучшение мини-CRUD

Цель - добавить обработку ошибок в функции из предыдущего раздела.

Шаги:

  1. Возьмите функции add_student, show_students, update_student_age, delete_student.
  2. В каждую функцию добавьте try-except-finally.
  3. Для ошибок базы используйте sqlite3.Error.
  4. Убедитесь, что соединение закрывается в finally.
  5. Проверьте работу каждой функции.

Ожидаемый результат: мини-CRUD продолжает работать, но теперь корректно сообщает об ошибках базы данных.


3. Улучшение структуры программы ​

3.1 Теория ​

Почему код нужно структурировать

В первых примерах каждое действие открывало соединение с базой, создавало курсор, выполняло запрос и закрывало соединение. Это рабочий вариант, но при росте программы появляется повторение.

Например, во многих функциях повторяются одни и те же строки:

python
conn = sqlite3.connect(DB_NAME)
cursor = conn.cursor()

И в конце снова повторяется:

python
conn.commit()
conn.close()

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

Контекстный менеджер with

В Python для работы с ресурсами часто используется конструкция with. Она помогает автоматически закрывать соединение и делает код короче.

Пример:

python
import sqlite3

DB_NAME = 'school.db'

with sqlite3.connect(DB_NAME) as conn:
    cursor = conn.cursor()
    cursor.execute('SELECT id, name, age FROM students')
    rows = cursor.fetchall()

for row in rows:
    print(row)

Разбор:

  1. with sqlite3.connect(DB_NAME) as conn открывает соединение с базой.
  2. Внутри блока создаётся курсор.
  3. Через курсор выполняется SQL-запрос.
  4. После выхода из блока with соединение закрывается автоматически.

Если внутри блока выполнялись изменения, with сохранит их при успешном завершении. Если возникнет ошибка, изменения будут отменены.

Улучшенная версия функций

Теперь функции можно записать короче:

python
import sqlite3

DB_NAME = 'school.db'


def init_db():
    with sqlite3.connect(DB_NAME) as conn:
        cursor = conn.cursor()

        cursor.execute('''
        CREATE TABLE IF NOT EXISTS students (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            age INTEGER
        )
        ''')


def add_student(name, age):
    with sqlite3.connect(DB_NAME) as conn:
        cursor = conn.cursor()

        cursor.execute(
            'INSERT INTO students (name, age) VALUES (?, ?)',
            (name, age)
        )


def get_students():
    with sqlite3.connect(DB_NAME) as conn:
        cursor = conn.cursor()

        cursor.execute('SELECT id, name, age FROM students')
        return cursor.fetchall()

Что изменилось

  • В функциях больше нет явного conn.close().
  • Код стал короче.
  • Функция get_students() не печатает данные, а возвращает их.
  • Вывод на экран можно сделать отдельно.

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

Разделение логики

В простой программе можно писать всё в одном файле. Но даже в одном файле полезно разделять код по смыслу:

  • функции для работы с базой;
  • функции для вывода данных;
  • функции для обработки ввода пользователя;
  • основной цикл программы.

Такой подход делает программу понятнее и облегчает поиск ошибок.

Пример функции вывода

python
def print_students(students):
    if not students:
        print('Студентов пока нет')
        return

    for student_id, name, age in students:
        print(f'{student_id}. {name}, возраст: {age}')

Разбор:

  1. Функция получает список студентов.
  2. Если список пустой, выводится сообщение.
  3. Если записи есть, они выводятся в удобном виде.

Частые ошибки

  • Смешивать SQL-запросы, ввод пользователя и вывод в одной большой функции.
  • Делать функцию, которая одновременно добавляет, читает и печатает данные.
  • Возвращать данные из функции, но потом нигде их не использовать.
  • Забывать, что fetchall() возвращает список строк.

3.2 Практика ​

Задание 1. Переписать функции через with

Цель - упростить код работы с соединением.

Шаги:

  1. Возьмите функции из раздела 1.
  2. Перепишите init_db(), add_student(), show_students() через with sqlite3.connect(...) as conn.
  3. Уберите явные вызовы conn.close().
  4. Проверьте, что программа работает как раньше.

Ожидаемый результат: код стал короче, но функциональность не изменилась.

Задание 2. Разделить чтение и вывод

Цель - отделить получение данных от вывода на экран.

Шаги:

  1. Создайте функцию get_students(), которая возвращает список студентов.
  2. Создайте функцию print_students(students), которая выводит этот список.
  3. Вызовите students = get_students().
  4. Передайте результат в print_students(students).

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


4. Итоговая консольная программа ​

4.1 Теория ​

Что должна уметь итоговая программа

Итоговая программа этого модуля должна объединить все изученные действия:

  • создать таблицу при запуске;
  • показать меню;
  • добавить студента;
  • вывести список студентов;
  • изменить возраст студента;
  • удалить студента;
  • обработать ошибки ввода;
  • обработать ошибки базы данных.

Это уже полноценный учебный пример программы с SQLite.

Полный пример программы

python
import sqlite3

DB_NAME = 'school.db'


def init_db():
    with sqlite3.connect(DB_NAME) as conn:
        cursor = conn.cursor()

        cursor.execute('''
        CREATE TABLE IF NOT EXISTS students (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            age INTEGER NOT NULL
        )
        ''')


def add_student(name, age):
    with sqlite3.connect(DB_NAME) as conn:
        cursor = conn.cursor()

        cursor.execute(
            'INSERT INTO students (name, age) VALUES (?, ?)',
            (name, age)
        )


def get_students():
    with sqlite3.connect(DB_NAME) as conn:
        cursor = conn.cursor()

        cursor.execute('SELECT id, name, age FROM students')
        return cursor.fetchall()


def update_student_age(student_id, new_age):
    with sqlite3.connect(DB_NAME) as conn:
        cursor = conn.cursor()

        cursor.execute(
            'UPDATE students SET age = ? WHERE id = ?',
            (new_age, student_id)
        )

        return cursor.rowcount


def delete_student(student_id):
    with sqlite3.connect(DB_NAME) as conn:
        cursor = conn.cursor()

        cursor.execute(
            'DELETE FROM students WHERE id = ?',
            (student_id,)
        )

        return cursor.rowcount


def print_students(students):
    if not students:
        print('Студентов пока нет')
        return

    for student_id, name, age in students:
        print(f'{student_id}. {name}, возраст: {age}')


def print_menu():
    print()
    print('1. Добавить студента')
    print('2. Показать студентов')
    print('3. Изменить возраст')
    print('4. Удалить студента')
    print('0. Выход')


def main():
    init_db()

    while True:
        print_menu()
        choice = input('Выберите действие: ')

        try:
            if choice == '1':
                name = input('Введите имя: ')
                age = int(input('Введите возраст: '))
                add_student(name, age)
                print('Студент добавлен')

            elif choice == '2':
                students = get_students()
                print_students(students)

            elif choice == '3':
                student_id = int(input('Введите id студента: '))
                new_age = int(input('Введите новый возраст: '))
                updated_count = update_student_age(student_id, new_age)

                if updated_count == 0:
                    print('Студент с таким id не найден')
                else:
                    print('Возраст обновлён')

            elif choice == '4':
                student_id = int(input('Введите id студента: '))
                deleted_count = delete_student(student_id)

                if deleted_count == 0:
                    print('Студент с таким id не найден')
                else:
                    print('Студент удалён')

            elif choice == '0':
                print('Программа завершена')
                break

            else:
                print('Неизвестная команда')

        except ValueError:
            print('Введите число там, где оно требуется')
        except sqlite3.Error as error:
            print('Ошибка базы данных:', error)


main()

Разбор структуры программы

Программа состоит из нескольких частей:

  • init_db() создаёт таблицу;
  • add_student() добавляет запись;
  • get_students() получает список студентов;
  • update_student_age() изменяет возраст;
  • delete_student() удаляет запись;
  • print_students() отвечает за вывод списка;
  • print_menu() выводит меню;
  • main() управляет всей программой.

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

Что делает rowcount

В функциях update_student_age() и delete_student() используется cursor.rowcount.

python
return cursor.rowcount

rowcount показывает, сколько строк было изменено или удалено.

Если значение равно 0, значит запись с таким id не найдена.

Это позволяет вывести пользователю понятное сообщение.

Почему меню находится в цикле while True

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

Пользователь может:

  • добавить запись;
  • посмотреть список;
  • изменить запись;
  • удалить запись;
  • выйти из программы через команду 0.

Команда break завершает цикл и останавливает программу.

Частые ошибки

  • Забыть вызвать init_db() перед работой с таблицей.
  • Не преобразовать id и возраст в int.
  • Не обработать ситуацию, когда запись с нужным id не найдена.
  • Разместить весь код в одном большом цикле без функций.
  • Забыть проверить программу через SQLiteStudio.

4.2 Практика ​

Задание 1. Запустить итоговую программу

Цель - проверить полный цикл работы с базой данных.

Шаги:

  1. Создайте файл lesson5_final.py.
  2. Перенесите в него полный пример программы.
  3. Запустите программу.
  4. Добавьте нескольких студентов.
  5. Выведите список студентов.
  6. Проверьте файл school.db через SQLiteStudio.

Ожидаемый результат: данные добавляются из Python и видны в SQLiteStudio.

Задание 2. Проверить изменение и удаление

Цель - убедиться, что UPDATE и DELETE работают корректно.

Шаги:

  1. Добавьте двух студентов.
  2. Измените возраст одного из них.
  3. Удалите второго.
  4. Выведите список студентов.
  5. Проверьте результат в SQLiteStudio.

Ожидаемый результат: один студент остаётся в таблице с обновлённым возрастом.

Задание 3. Добавить поле группы

Цель - самостоятельно расширить таблицу и программу.

Шаги:

  1. Создайте новую базу или удалите старый файл school.db.
  2. Добавьте в таблицу поле group_name.
  3. Измените функцию add_student(), чтобы она принимала группу.
  4. Измените get_students() и print_students().
  5. Проверьте, что группа выводится вместе с именем и возрастом.

Ожидаемый результат: программа хранит и выводит группу студента.

Задание 4. Итоговая самостоятельная доработка

Цель - закрепить работу с полноценной программой.

Выберите одно улучшение:

  • добавить поиск студента по имени;
  • добавить удаление всех студентов;
  • добавить проверку, что возраст больше нуля;
  • добавить таблицу groups;
  • добавить сохранение даты добавления студента.

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