- Обзор технологии
- Установка и настройка
- Базовые операции
- Создание таблиц
- Работа с данными
- Создание связей
- Создание запросов
- Создание форм
- Работа с VBA кодом
- Типичные ошибки и решения
- Лучшие практики
- Полный пример
- Python 3.x - основной язык программирования
- pywin32 (win32com) - библиотека для работы с COM объектами Windows
- MS Access - целевое приложение (Office 2007+)
Python через COM (Component Object Model) управляет приложением MS Access как если бы пользователь работал вручную. Мы получаем доступ к объектной модели Access и можем:
- Создавать/открывать базы данных
- Выполнять SQL команды
- Создавать таблицы, запросы, формы, отчёты
- Управлять VBA кодом
- Экспортировать/импортировать объекты
pip install pywin32import win32com.client
import osimport sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')Важно: Без этого Unicode символы (✓, ─, и т.д.) вызовут ошибку UnicodeEncodeError в консоли Windows с кодировкой cp1251.
access = win32com.client.Dispatch("Access.Application")
access.Visible = False # True для отладкиDB_PATH = r"C:\path\to\database.accdb"
access.NewCurrentDatabase(DB_PATH)access.OpenCurrentDatabase(DB_PATH)access.CloseCurrentDatabase()
access.Quit()Важно: Всегда закрывайте Access в блоке finally или используйте try/except, иначе процесс MSACCESS.EXE останется висеть в памяти.
taskkill /F /IM MSACCESS.EXEsql = """
CREATE TABLE Товары (
КодТовара COUNTER PRIMARY KEY,
Наименование TEXT(100) NOT NULL,
Количество DOUBLE,
Цена CURRENCY,
ДатаПоступления DATETIME
);
"""
access.CurrentDb().Execute(sql)| Тип Access | SQL синтаксис | Описание |
|---|---|---|
| Счётчик | COUNTER или AUTOINCREMENT |
Автоинкремент |
| Текст | TEXT(n) или VARCHAR(n) |
Строка до 255 символов |
| Поле МЕМО | MEMO или LONGTEXT |
Длинный текст |
| Числовой | LONG, SHORT, DOUBLE, SINGLE |
Числа |
| Денежный | CURRENCY |
Денежный формат |
| Дата/время | DATETIME |
Дата и время |
| Логический | YESNO или BIT |
Да/Нет |
sql = "CREATE TABLE [Мои Данные] ([Код записи] COUNTER PRIMARY KEY);"# Важно: даты в формате #MM/DD/YYYY#
from datetime import datetime
date = datetime(2024, 6, 15)
date_str = date.strftime("#%m/%d/%Y#")
sql = f"""
INSERT INTO Товары (Наименование, Количество, Цена, ДатаПоступления)
VALUES ('Нарзан 0.5л', 100, 45.50, {date_str});
"""
access.CurrentDb().Execute(sql)rs = access.CurrentDb().OpenRecordset("SELECT * FROM Товары")
while not rs.EOF:
print(rs.Fields("Наименование").Value)
rs.MoveNext()
rs.Close()sql = "UPDATE Товары SET Цена = 50.00 WHERE КодТовара = 1;"
access.CurrentDb().Execute(sql)sql = "DELETE FROM Товары WHERE КодТовара = 1;"
access.CurrentDb().Execute(sql)# Создаём связь между таблицами "Товары" и "Продажи"
relation = access.CurrentDb().CreateRelation(
"rel_Товары_Продажи", # Имя связи
"Товары", # Главная таблица
"Продажи", # Подчинённая таблица
0 # Атрибуты (0 = без каскадного удаления)
)
# Добавляем поле связи
field = relation.CreateField("КодТовара")
field.ForeignName = "КодТовара"
relation.Fields.Append(field)
# Добавляем связь в базу
access.CurrentDb().Relations.Append(relation)| Значение | Константа | Описание |
|---|---|---|
| 0 | - | Без каскадных операций |
| 1 | dbRelationUnique | Связь один-к-одному |
| 2 | dbRelationDontEnforce | Не обеспечивать целостность |
| 256 | dbRelationInherited | Наследуемая связь |
| 4096 | dbRelationUpdateCascade | Каскадное обновление |
| 8192 | dbRelationDeleteCascade | Каскадное удаление |
query_sql = """
SELECT Товары.Наименование, Продажи.Дата, Продажи.Количество,
[Количество]*[Цена] AS Сумма
FROM Товары INNER JOIN Продажи ON Товары.КодТовара = Продажи.КодТовара;
"""
qd = access.CurrentDb().CreateQueryDef("Запрос_Продажи", query_sql.strip())# Параметр в квадратных скобках с двоеточием
query_sql = """
SELECT * FROM Товары
WHERE Наименование LIKE [Введите название:] & '*';
"""
access.CurrentDb().CreateQueryDef("Запрос_Поиск", query_sql)При выполнении такого запроса Access покажет диалог ввода параметра.
acForm = 2
acTable = 0
acQuery = 1
acTextBox = 109
acLabel = 100
acCommandButton = 104
acComboBox = 111
acLine = 102
acDetail = 0 # Секция "Область данных"
acHeader = 1 # Заголовок формы
acFooter = 2 # Примечание формы
acSaveYes = 1
acDesign = 1 # Режим конструктора
acNormal = 0 # Обычный режимform = access.CreateForm()
form_name = form.Name # Автоматически сгенерированное имя
# Привязка к таблице
form.RecordSource = "Товары"
# Настройки формы
form.Caption = "Форма товаров"
form.DefaultView = 0 # Single Form
form.NavigationButtons = True
form.RecordSelectors = True
form.AutoCenter = True
# Настройка секции
section = form.Section(acDetail)
section.Height = 5000 # В твипах (1440 твипов = 1 дюйм)
section.BackColor = 0xFFF8F0 # Цвет в формате BGR# Функция для установки свойств (обход ограничений COM)
def set_prop(ctrl, prop_name, value):
try:
ctrl.Properties(prop_name).Value = value
except:
pass
# Создание Label
label = access.CreateControl(
form_name, # Имя формы
acLabel, # Тип элемента
acDetail, # Секция
"", # Родительский элемент
"", # Источник данных
100, # Left (твипы)
100, # Top (твипы)
2000, # Width (твипы)
300 # Height (твипы)
)
set_prop(label, "Caption", "Наименование:")
set_prop(label, "FontSize", 10)
# Создание TextBox (связанного с полем)
textbox = access.CreateControl(
form_name,
acTextBox,
acDetail,
"",
"Наименование", # Имя поля из RecordSource
2200,
100,
4000,
300
)
set_prop(textbox, "FontSize", 10)
set_prop(textbox, "BackColor", 0xFFFFFF)
# Создание ComboBox
combo = access.CreateControl(form_name, acComboBox, acDetail, "", "КодТовара", 2200, 500, 4000, 300)
set_prop(combo, "RowSourceType", "Table/Query")
set_prop(combo, "RowSource", "SELECT КодТовара, Наименование FROM Товары;")
set_prop(combo, "ColumnCount", 2)
set_prop(combo, "ColumnWidths", "500;3000")
set_prop(combo, "BoundColumn", 1)
# Создание CommandButton
button = access.CreateControl(form_name, acCommandButton, acDetail, "", "", 100, 1000, 1500, 400)
set_prop(button, "Name", "cmdSave")
set_prop(button, "Caption", "Сохранить")
set_prop(button, "BackColor", 0xADD8E6)# Закрываем с сохранением
access.DoCmd.Close(acForm, form_name, acSaveYes)
# Переименовываем
access.DoCmd.Rename("Форма_Товары", acForm, form_name)vbe = access.VBE
project = vbe.VBProjects(1) # Первый (обычно единственный) проект| Type | Описание |
|---|---|
| 1 | Стандартный модуль (Module) |
| 2 | Модуль класса (Class Module) |
| 3 | Форма UserForm |
| 100 | Документ (форма/отчёт Access) |
module = project.VBComponents.Add(1) # 1 = стандартный модуль
module.Name = "modUtils"
vba_code = '''
Option Compare Database
Option Explicit
Public Sub TestSub()
MsgBox "Hello!"
End Sub
'''
module.CodeModule.AddFromString(vba_code)Для работы кнопок код должен быть в модуле самой формы, а не в отдельном модуле.
form_name = "Форма_Товары"
# Открываем форму в режиме конструктора
access.DoCmd.OpenForm(form_name, acDesign)
# Включаем модуль формы
frm = access.Forms(form_name)
frm.HasModule = True
# Находим компонент формы в VBE
vbe = access.VBE
proj = vbe.VBProjects(1)
for j in range(1, proj.VBComponents.Count + 1):
comp = proj.VBComponents(j)
if comp.Name == "Form_" + form_name: # Внимание: префикс "Form_"
code_module = comp.CodeModule
# Очищаем существующий код
if code_module.CountOfLines > 0:
code_module.DeleteLines(1, code_module.CountOfLines)
# Добавляем новый код
vba_code = '''Option Compare Database
Private Sub cmdSave_Click()
On Error Resume Next
DoCmd.RunCommand acCmdSaveRecord
End Sub
Private Sub cmdNew_Click()
On Error Resume Next
DoCmd.GoToRecord , , acNewRec
End Sub
'''
code_module.AddFromString(vba_code)
break
# Сохраняем форму
access.DoCmd.Close(acForm, form_name, acSaveYes)# Открываем форму в режиме конструктора
access.DoCmd.OpenForm(form_name, acDesign)
frm = access.Forms(form_name)
# Устанавливаем обработчик события
frm.Controls("cmdSave").OnClick = "[Event Procedure]"
access.DoCmd.Close(acForm, form_name, acSaveYes)for j in range(proj.VBComponents.Count, 0, -1):
comp = proj.VBComponents(j)
if comp.Name == "modButtons" and comp.Type == 1:
proj.VBComponents.Remove(comp)
breakОшибка:
UnicodeEncodeError: 'charmap' codec can't encode character '\u2713'
Решение:
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')Ошибка:
PermissionError: [WinError 32] Процесс не может получить доступ к файлу
Причина: Access процесс не завершился.
Решение:
# В начале скрипта
import subprocess
subprocess.run(['taskkill', '/F', '/IM', 'MSACCESS.EXE'],
capture_output=True, shell=True)Ошибка:
AttributeError: object has no attribute 'Caption'
Причина: COM объекты Access не всегда позволяют напрямую устанавливать свойства.
Решение: Использовать Properties():
def set_prop(ctrl, prop_name, value):
try:
ctrl.Properties(prop_name).Value = value
except:
passОшибка:
Compile error: Duplicate Option statement
Причина: Код добавлялся несколько раз без очистки.
Решение:
# Сначала очищаем модуль
if code_module.CountOfLines > 0:
code_module.DeleteLines(1, code_module.CountOfLines)
# Затем добавляем код
code_module.AddFromString(vba_code)Причина: Код в отдельном модуле (Public Sub), а не в модуле формы (Private Sub).
Решение:
- Код должен быть в модуле формы (
Form_ИмяФормы) - Процедуры должны быть
Private Sub, а неPublic Sub - Имя процедуры должно точно совпадать:
cmdButton_Click - Свойство OnClick должно быть
[Event Procedure]
Ошибка:
pywintypes.com_error: Аргумент "Тип объекта" недопустим
Решение: Не использовать DoCmd.Save(), вместо этого:
access.DoCmd.Close(acForm, form_name, acSaveYes)Причина: Trust Center блокирует макросы.
Решение для пользователя:
- При открытии базы нажать "Включить содержимое"
- Или: Файл → Параметры → Центр управления безопасностью → Включить все макросы
# -*- coding: utf-8 -*-
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
import win32com.client
import os
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "database.accdb")
access = None
try:
access = win32com.client.Dispatch("Access.Application")
access.Visible = True # Для отладки
# ... ваш код ...
finally:
if access:
try:
access.CloseCurrentDatabase()
except:
pass
access.Quit()Это позволяет видеть, что происходит в Access, и отлавливать диалоговые окна.
access.DoCmd.Close(acForm, form_name, acSaveYes)if os.path.exists(DB_PATH):
os.remove(DB_PATH)sql = "SELECT * FROM [Товары] WHERE [Наименование] = 'Нарзан';"В модуле формы должен быть только один Option Compare Database в самом начале.
# -*- coding: utf-8 -*-
"""
Полный пример создания базы данных Access с формами и кнопками
"""
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
import win32com.client
import os
from datetime import datetime, timedelta
import random
# Константы
acForm = 2
acTextBox = 109
acLabel = 100
acCommandButton = 104
acDetail = 0
acSaveYes = 1
acDesign = 1
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "example.accdb")
def set_prop(ctrl, prop, val):
try:
ctrl.Properties(prop).Value = val
except:
pass
# Удаляем старую базу
if os.path.exists(DB_PATH):
os.remove(DB_PATH)
access = None
try:
# Создаём Access
access = win32com.client.Dispatch("Access.Application")
access.Visible = True
access.NewCurrentDatabase(DB_PATH)
# === ТАБЛИЦА ===
sql = """
CREATE TABLE Товары (
Код COUNTER PRIMARY KEY,
Название TEXT(100),
Цена CURRENCY,
Дата DATETIME
);
"""
access.CurrentDb().Execute(sql)
# === ДАННЫЕ ===
for i in range(10):
date = datetime.now() - timedelta(days=random.randint(1, 30))
date_str = date.strftime("#%m/%d/%Y#")
sql = f"INSERT INTO Товары (Название, Цена, Дата) VALUES ('Товар {i+1}', {random.randint(10, 100)}, {date_str});"
access.CurrentDb().Execute(sql)
# === ФОРМА ===
form = access.CreateForm()
fname = form.Name
form.RecordSource = "Товары"
form.Caption = "Форма товаров"
form.Section(acDetail).Height = 3000
# Поля
y = 200
for field in ["Код", "Название", "Цена", "Дата"]:
lbl = access.CreateControl(fname, acLabel, acDetail, "", "", 200, y, 1500, 300)
set_prop(lbl, "Caption", field + ":")
txt = access.CreateControl(fname, acTextBox, acDetail, "", field, 1800, y, 3000, 300)
y += 400
# Кнопки
y += 200
for btn_name, btn_text, x in [("cmdPrev", "< Назад", 200), ("cmdNext", "Вперед >", 1500), ("cmdNew", "Новая", 2800)]:
btn = access.CreateControl(fname, acCommandButton, acDetail, "", "", x, y, 1200, 350)
set_prop(btn, "Name", btn_name)
set_prop(btn, "Caption", btn_text)
access.DoCmd.Close(acForm, fname, acSaveYes)
access.DoCmd.Rename("Форма_Товары", acForm, fname)
# === VBA КОД ===
access.DoCmd.OpenForm("Форма_Товары", acDesign)
frm = access.Forms("Форма_Товары")
frm.HasModule = True
vbe = access.VBE
proj = vbe.VBProjects(1)
for j in range(1, proj.VBComponents.Count + 1):
comp = proj.VBComponents(j)
if comp.Name == "Form_Форма_Товары":
cm = comp.CodeModule
if cm.CountOfLines > 0:
cm.DeleteLines(1, cm.CountOfLines)
vba = '''Option Compare Database
Private Sub cmdPrev_Click()
On Error Resume Next
DoCmd.GoToRecord , , acPrevious
End Sub
Private Sub cmdNext_Click()
On Error Resume Next
DoCmd.GoToRecord , , acNext
End Sub
Private Sub cmdNew_Click()
On Error Resume Next
DoCmd.GoToRecord , , acNewRec
End Sub
'''
cm.AddFromString(vba)
break
# Привязываем события
frm.Controls("cmdPrev").OnClick = "[Event Procedure]"
frm.Controls("cmdNext").OnClick = "[Event Procedure]"
frm.Controls("cmdNew").OnClick = "[Event Procedure]"
access.DoCmd.Close(acForm, "Форма_Товары", acSaveYes)
print("База данных создана успешно!")
print(f"Файл: {DB_PATH}")
finally:
if access:
access.CloseCurrentDatabase()
access.Quit()- create_database - создание новой базы данных
- execute_sql - выполнение SQL запросов
- create_table - создание таблицы (высокоуровневый)
- create_form - создание формы с полями
- add_form_buttons - добавление кнопок с VBA
- create_query - создание запроса
- create_relation - создание связи
- export_form - экспорт формы в текст
- import_form - импорт формы из текста
- Управление жизненным циклом Access - один экземпляр на сессию
- Обработка диалоговых окон - Visible=True и уведомления пользователю
- Транзакции - сохранение после каждой операции
- Валидация - проверка существования объектов
- Логирование - подробный вывод операций
Документ создан на основе практического опыта автоматизации MS Access через Python + COM. Дата: Январь 2025