2026, Jan 14 00:03
AttributeError в Python GUI: как исправить вызов методов DAO и порядок инициализации SQLite
В Python GUI на CustomTkinter возникает AttributeError при вызове методов DAO из-за self и порядка инициализации. Покажем сбой и рабочее решение для SQLite.
Когда GUI на Python обращается к слою доступа к данным, легко упустить из виду, что метод экземпляра ожидает именно экземпляр. Частая ошибка — передать объект интерфейса там, где нужен объект работы с базой, и в итоге получить AttributeError, выглядящий поначалу загадочно. В этом разборе мы покажем, почему так происходит, продемонстрируем точный сценарий сбоя и аккуратно его исправим. Заодно разберём подводный камень с порядком инициализации, из‑за которого незаметно ломается работа с SQLite.
Подготовка: неверный вызов метода DAO
Ниже — минимальный объект доступа к данным для SQLite3 и фрейм CustomTkinter, который пытается получить записи. Конструкция в целом корректная, но в неё закрались две тонкие ошибки.
import sqlite3
class Store:
VEHICLES_DDL = """
CREATE TABLE IF NOT EXISTS vehicles (
vehicle_key integer NOT NULL PRIMARY KEY AUTOINCREMENT,
vehicle_description TEXT NOT NULL
)
"""
SQL_VEHICLE_INSERT = "INSERT INTO vehicles (vehicle_description) VALUES (?)"
SQL_VEHICLE_SELECT_ALL = "SELECT * FROM vehicles"
MILEAGE_DDL = """
CREATE TABLE IF NOT EXISTS milage (
milage_date DATE NOT NULL PRIMARY KEY,
vehicle_id INTEGER NOT NULL,
milage integer NOT NULL
)
"""
SQL_MILEAGE_INSERT = "INSERT INTO milage ( milage_date , vehicle_id , milage ) VALUES (? , ? , ? )"
def __init__(self, dbname = "vehicles.sqlite3"):
# здесь dbname устанавливается слишком поздно
self.setup_table(self.MILEAGE_DDL)
self.setup_table(self.VEHICLES_DDL)
self.dbname = dbname
def setup_table(self, sql):
with sqlite3.connect(self.dbname) as conn:
cur = conn.cursor()
cur.execute(sql)
def insert_vehicle(self, description):
with sqlite3.connect(self.dbname) as conn:
cur = conn.cursor()
cur.execute(self.SQL_VEHICLE_INSERT, (description,))
def insert_milage(self, milage_date, vehicle_id, milage):
with sqlite3.connect(self.dbname) as conn:
cur = conn.cursor()
cur.execute(self.SQL_MILEAGE_INSERT, (milage_date, vehicle_id, milage))
def fetch_vehicles(self):
with sqlite3.connect(self.dbname) as conn:
cur = conn.cursor()
cur.execute(self.SQL_VEHICLE_SELECT_ALL)
return cur.fetchall()
import customtkinter as ctk
from store import Store
class MileageInputView(ctk.CTkFrame):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
self.section = ctk.CTkFrame(self)
self.section.pack(fill="x", padx=5, pady=25)
# Неверно: передаётся экземпляр GUI туда, где ожидается экземпляр Store
records = Store.fetch_vehicles(self)
Сбой проявляется как AttributeError, указывающий, что у GUI-объекта нет нужного атрибута базы данных:
AttributeError: 'MilageEntryFrame' object has no attribute 'dbname'
Что происходит на самом деле
В Python self — лишь условное имя первого параметра метода экземпляра. Интерпретатор автоматически передаёт экземпляр, когда вы вызываете метод у экземпляра. Если же вы обращаетесь к методу у класса, первый аргумент нужно передать вручную. И этот аргумент должен быть экземпляром того самого класса, чей метод вызывается; иначе поиск атрибутов внутри метода пойдёт по «чужому» объекту.
>>> class Sample:
... def task(self):
... pass
...
>>> type(Sample.task)
<class 'function'>
>>> type(Sample().task)
<class 'method'>
В примере с GUI вызов Store.fetch_vehicles(self) заставляет Python передать экземпляр MileageInputView первым аргументом метода fetch_vehicles. Внутри fetch_vehicles код обращается к self.dbname, но self — это уже фрейм интерфейса, а не объект доступа к данным, поэтому атрибута dbname там нет. Отсюда и AttributeError.
Есть и вторая, отдельная проблема в объекте доступа к данным: dbname присваивается после того, как в конструкторе уже вызываются методы, которые от него зависят. setup_table открывает sqlite3.connect(self.dbname), но self.dbname устанавливается лишь позже. Даже если исправить первую ошибку, такой порядок инициализации рано или поздно сломает работу с БД.
Исправление: создаём экземпляр DAO и инициализируем его правильно
Во‑первых, вызывайте метод экземпляра на реальном экземпляре класса доступа к данным. Во‑вторых, убедитесь, что путь к базе задан в объекте до вызова любых методов, которые от него зависят. В исправленном коде ниже учтены оба пункта без изменения логики.
import sqlite3
class Store:
VEHICLES_DDL = """
CREATE TABLE IF NOT EXISTS vehicles (
vehicle_key integer NOT NULL PRIMARY KEY AUTOINCREMENT,
vehicle_description TEXT NOT NULL
)
"""
SQL_VEHICLE_INSERT = "INSERT INTO vehicles (vehicle_description) VALUES (?)"
SQL_VEHICLE_SELECT_ALL = "SELECT * FROM vehicles"
MILEAGE_DDL = """
CREATE TABLE IF NOT EXISTS milage (
milage_date DATE NOT NULL PRIMARY KEY,
vehicle_id INTEGER NOT NULL,
milage integer NOT NULL
)
"""
SQL_MILEAGE_INSERT = "INSERT INTO milage ( milage_date , vehicle_id , milage ) VALUES (? , ? , ? )"
def __init__(self, dbname = "vehicles.sqlite3"):
# Сначала установить, затем использовать
self.dbname = dbname
self.setup_table(self.MILEAGE_DDL)
self.setup_table(self.VEHICLES_DDL)
def setup_table(self, sql):
with sqlite3.connect(self.dbname) as conn:
cur = conn.cursor()
cur.execute(sql)
def insert_vehicle(self, description):
with sqlite3.connect(self.dbname) as conn:
cur = conn.cursor()
cur.execute(self.SQL_VEHICLE_INSERT, (description,))
def insert_milage(self, milage_date, vehicle_id, milage):
with sqlite3.connect(self.dbname) as conn:
cur = conn.cursor()
cur.execute(self.SQL_MILEAGE_INSERT, (milage_date, vehicle_id, milage))
def fetch_vehicles(self):
with sqlite3.connect(self.dbname) as conn:
cur = conn.cursor()
cur.execute(self.SQL_VEHICLE_SELECT_ALL)
return cur.fetchall()
import customtkinter as ctk
from store import Store
class MileageInputView(ctk.CTkFrame):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
self.section = ctk.CTkFrame(self)
self.section.pack(fill="x", padx=5, pady=25)
# Правильно: создать экземпляр и вызвать метод
records = Store().fetch_vehicles()
Почему это важно для поддерживаемого кода на Python
Понимание того, как работают привязанные методы, критично при стыковке слоя интерфейса и слоя доступа к данным. Это предотвращает тонкие ошибки, когда в метод «просачивается» не тот объект, и избавляет от вводящих в заблуждение сообщений о «пропавших» атрибутах у не связанных классов. Не менее важно соблюдать порядок инициализации: атрибуты должны существовать до того, как их используют зависимые методы, особенно если те обращаются к внешним системам вроде базы SQLite.
Выводы
Всегда вызывайте методы экземпляра на экземплярах, а не на классе, и не подставляйте чужой объект вместо self. Задавайте критичные атрибуты — например путь к базе — до любых зависимых вызовов. Соблюдая эти два принципа, вы добьётесь корректного взаимодействия виджетов CustomTkinter и DAO на SQLite, а неожиданные AttributeError из‑за dbname не возникнут.