Python

индекс
250,37

Пример разработки небольшого python+PyQt4 приложения для учетной системы

Часто приходится разрабатывать приложения для корпоративной системы которые должны были функционировать еще вчера, не требующие строго соответствия корпоративным стандартам. Такими приложениями могут представлять cms к сайтам, gui для сервисов под *nix системы просто приложением учетной системы. Разработка приложений подобного рода на скриптовых языках есть тема! обычно оптимальна с точки зрения скорости выполнения. Под катом пример реализации приложения на python+PyQt4, функции приложения парсинг и загрузка данных из xls файлов...
Для того чтобы приготовить яичницу я использовал:
  1. Python2.5
  2. PyQt-4.4.3
  3. kinterbasdb3 (БД КИС — Firebird)
  4. xlrd-0.6
В качестве редактора всем рекомендую emacs python-mode (это касается и
виндопользователей):

emsc python-mode

1. Достаем посуду из шкафа (хранение настроек)


Вообще для скриптовых языков такой вопрос не стоит так как, вы можете завести файл настроек типа settings.py и спокойно запускать его на уровне кода:
# -*- coding: cp1251 -*-
host='localhost/3051:intur'
user='SYSDBA'
path='d:\\dlogs\\scanner\\xls'

oprts = (
u'НАТАЛИ',
u'ЭКСПРЕСС',
u'ИНТАЕР',
u'ЛАНТА',
u'КОРАЛ',
)

Получение переменных в программе выглядит так:
mod = __import__(modName)
host = getattr(mod, 'host', 'horn:intur')
user = getattr(mod, 'user', 'SYSDBA')

2.Рисование форм


Qt Software — вообще красавцы с точки зрения документации, или если сравнивать тулзы для рисования GUI — то здесь они тоже молодцы. Главное окошко программы я нарисовал в QtDesigner + отметил сигналы с виджетов, которые нужно обрабатывать.
emsc python-mode
Для того чтобы быстро собирать формы в дизайнере безусловно нужно немного поднатореть в понимание механизмов формирования Layout и Spacers. Существует два варианта поднятия окошка в приложения, либо вы генерируете файл необходимом для вас языке программирования, либо с помощью спец.модулей Qt строите классы из xml-файлов описания. Я обычно генерирую файл, не могу сказать что это чем-то лучше, просто мне это кажется более удобным. Для питона PyQt поставляет pyuic.bat который просто нужно скопировать в директорию вашего проекта и генерировать классы-обработчики окошек по мере вашей работы в дизайнере
У меня это для главного окна выглядит так:

       pyuic.bat -o mwnd.py mwnd.ui

В отличие от wx Qt генеруют не наследники классов главных окон, а классы- модификаторы которые получают в качестве аргумента базовый класс окна и «вешают» на него ваши контролы. При генерации на с++ Qt использует множественное наследование класс вашего приложения становится наследником класса базового окна и класса-модификатора. Интересно что при генерации файлов слоты pyuic вешаются на базовый класс, отсюда несоответствие — обработчики сигналов находятся в наследнике базового класса окна, а ссылки на контролы этого же окна находятся у класса-модификатора сгенерированного pyuic.

    def _initApp(self):
        self.mwnd, self.mwnd.ui = MainWnd(), Uwnd()
        self.mwnd.ui.setupUi(self.mwnd)

Я поступил просто: связал класс-модификатор с наследником базового класса окна, получилось просто при необходимости обращения к контролам общаюсь к ним как — self.ui.«name». Вроде очевидная штука, но как часто это бывает пришла ко мне в голову не с первого раза.

! Еще интересно что в питон не переваривает ключевые слова в названиях атрибутов и методов классов, поэтому вы не можете завести переменную self.pass или self.exec(). Отсюда и смешная надпись в каждой PyQt приложении sys.exit(app.exec_())

3. Ставим скороводку на плиту, или запуск приложения


Вот код main.py — его задачи запуститься, загрузить файл настроек, запросить пароль и в случае удачного подключения запустить главное окно приложения

main.py

4. Не забудем положить яица в яичницу.


Наследник главного окна приложения (MainWnd) выполняет функции отслеживания поведения пользователя, здесь кстати видны слоты которые мы сгенерировали в дизайнере, здесь основная задача кода максимально прозрачные и простые функции управления окном для основного класса приложения выполняющего загрузку файла в БД.
Класс выполняющий основную функцию приложения — парсинг и загрузку данных (MainModel) не имеет смысла без главного окна поэтому сделаем его атрибутом окна. Для парсинга xls файлов в питоне есть тривиальная в использовании, и тем замечательная библиотека xlrd, работает с файлом напрямую не поднимая громоздких OLE машин, и поэтому еще и более быстродейственна. Приложение позволяет загружать в режиме только измененных файлов, поэтому часть когда MainModel, уведено для сравнения дат модификации и последней загрузки, которая хранится в БД. Так кроме главного рабочего класса приложения никто с БД не общается даем ему небольшой класс (dbConn), которые хранит инструкции MainModel в «терминах» sql.

WndModel.py

Вообще по науке, есть некая неуклюжесть программы из-за протягивания переменных (например app) с класса Container до классов 2 уровня иерархий. В больших программах таких переменных становится чуть более чем 9000 много, и эта задача решается с помощью синглтона (в следующий раз это я учту).

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

P.S. Полный исходный код здесь

UPD. вынес код на внешний кодохранитель, если кто подскажет как корректно подсветить python-код на Хабре буду благодарен.
+23
30 октября 2009, 01:29
48

комментарии (22)

НЛО прилетело и опубликовало эту надпись здесь
+2
bobry #
оффтоп: не поделитесь цветовой схемой пожалуйста? а то я уже столько перепробовал и все оставляют ощущение что «что то не так»
–4
muslimov #
(set-language-environment 'UTF-8)
(setq default-input-method 'russian-computer)

(set-selection-coding-system 'windows-1251)
(set-default-coding-systems 'windows-1251)
(prefer-coding-system 'windows-1251)

(custom-set-faces
;; custom-set-faces was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
'(default ((t (:inherit nil :stipple nil :background «gray17» :foreground «snow» :inverse-video nil :box nil :strike-through nil :overline nil :underline nil :slant normal :weight normal :height 98 :width normal :foundry «outline» :family «Courier New»))))
'(cursor ((t (:background «peach puff»))))
'(font-lock-comment-delimiter-face ((default (:inherit font-lock-comment-face)) (((class color) (min-colors 16)) (:background «green»))))
'(font-lock-comment-face ((nil nil)))
'(font-lock-doc-face ((t (:inherit font-lock-string-face :background «black»)))))

(setq auto-mode-alist
(append
'(
( "\\.el$". lisp-mode)
( "\\.py$". python-mode)
( "\\.html$". sgml-mode)
( "\\.xml$". sgml-mode)
)))

(global-font-lock-mode 1)

(autoload 'javascript-mode «javascript» nil t)
(add-to-list 'auto-mode-alist '("\\.js\\'". javascript-mode))

;; tool bar
(tool-bar-mode -1)

(set-background-color "#333333")
(set-foreground-color "#ffffff")

(setq scroll-step 1)
(global-hl-line-mode 1)

(windmove-default-keybindings 'meta)
(fset 'yes-or-no-p 'y-or-n-p)

(iswitchb-mode 1)
(global-set-key [?\C-,] 'previous-buffer)
(global-set-key [?\C-.] 'next-buffer)

(put 'upcase-region 'disabled nil)
(delete-selection-mode 1)

;;(setq inhibit-startup-message t)
(setq default-tab-width 4)

(desktop-save-mode t)
(global-set-key [f5] 'call-last-kbd-macro)
(global-set-key [f11] 'buffer-menu)
(global-set-key [f10] 'bookmark-bmenu-list)

(global-set-key [?\C-'] 'toggle-truncate-lines)
(put 'narrow-to-page 'disabled nil)

что не понравится (set-language-environment 'UTF-8)
(setq default-input-method 'russian-computer)

(set-selection-coding-system 'windows-1251)
(set-default-coding-systems 'windows-1251)
(prefer-coding-system 'windows-1251)

(custom-set-faces
;; custom-set-faces was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
'(default ((t (:inherit nil :stipple nil :background «gray17» :foreground «snow» :inverse-video nil :box nil :strike-through nil :overline nil :underline nil :slant normal :weight normal :height 98 :width normal :foundry «outline» :family «Courier New»))))
'(cursor ((t (:background «peach puff»))))
'(font-lock-comment-delimiter-face ((default (:inherit font-lock-comment-face)) (((class color) (min-colors 16)) (:background «green»))))
'(font-lock-comment-face ((nil nil)))
'(font-lock-doc-face ((t (:inherit font-lock-string-face :background «black»)))))

(setq auto-mode-alist
(append
'(
( "\\.el$". lisp-mode)
( "\\.py$". python-mode)
( "\\.html$". sgml-mode)
( "\\.xml$". sgml-mode)
)))

(global-font-lock-mode 1)

(autoload 'javascript-mode «javascript» nil t)
(add-to-list 'auto-mode-alist '("\\.js\\'". javascript-mode))

;; tool bar
(tool-bar-mode -1)

(set-background-color "#333333")
(set-foreground-color "#ffffff")

(setq scroll-step 1)
(global-hl-line-mode 1)

(windmove-default-keybindings 'meta)
(fset 'yes-or-no-p 'y-or-n-p)

(iswitchb-mode 1)
(global-set-key [?\C-,] 'previous-buffer)
(global-set-key [?\C-.] 'next-buffer)

(put 'upcase-region 'disabled nil)
(delete-selection-mode 1)

;;(setq inhibit-startup-message t)
(setq default-tab-width 4)

(desktop-save-mode t)
(global-set-key [f5] 'call-last-kbd-macro)
(global-set-key [f11] 'buffer-menu)
(global-set-key [f10] 'bookmark-bmenu-list)

(global-set-key [?\C-'] 'toggle-truncate-lines)
(put 'narrow-to-page 'disabled nil)

это часть .emacs которая для меня особо необходима. попробуйте.
+1
Mezomish #
Зря Вы простынёй это сюда запостили, лучше бы линк на файл… =\
0
traaance #
0
muslimov #
ок, ступил не спорю
+2
barbuza #
а не расскажете про упаковку приложения в py2exe? а то с pyqt4 постоянно какие-то проблемы возникают
+1
alexey_uzhva #
Обратите внимание на PyInstaller — малоизвестный инструмент, между тем превосходящий по фичам и удобству упомянутый py2exe.

Один из ключевых моментов — автоматически распознает и подключает PyQt/lxml и прочие библиотеки. Т.е. не надо делать абсолютно никаких телодвижений — оно работает «из коробки».

Впрочем, для объективности отмечу и минусы — для поддержки .manifest файлов требует патча (не знаю как с этим у py2exe), и лучше использовать SVN-ветку программы т.к. они почему-то уже год с лишним как не хотят выкладывать на публику версии своей утилиты, несмотря на то, что работает отлично и разработка идет, проект не мертв.
+7
alexey_uzhva #
Хотелось бы видеть в начале статьи краткую аннотацию с описанием того, что конкретно делает ваша программа.

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

Какая цель этой статьи? Кому должна быть полезна?

Тот кто не знает PyQt от такого изложения не поймет ничего. А кто знает, скажет вам, что код ваш откровенно средненького качества.

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

Если сейчас там есть какие-то достойные моменты — то их не видно в вашей куче. Если нет — то незачем писать статью.

В данный момент все это похоже на «Я вот сделал за вечер на коленке программку, посмотрите какой я молодец, вот ее исходник».
0
muslimov #
Я бы такую статью бы с удовольствием прочитал ранее, статья не салат-копипаст. полезна для тех, кто часто пишет комменты типа «скоро буду изучать питон».

>А кто знает, скажет вам, что код ваш откровенно средненького качества.
Вы подскажите что не так, я подучусь.

а скачивание кода сделайте архивом
а разве я не так сделал.

А вообще ваш коммент заменить на — «вы мне не нравитесь. я умнее.»
0
alexey_uzhva #
Уважаемый, ну зачем сразу же так агрессивно…

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

Лишь хочу сказать, что в данной статье не хватает (помимо подсветки кода) некоторой центральной идеи.

Говорю о том, что читателю вряд ли нужна точно такая программа, как есть у вас. Читатель хочет научиться чему-то, каким-то принципам, каким-то приемам.

Потому полезно ткнуть пальцем «вот тут сделано то-то так-то потому, что...». А учиться лишь по одному исходнику способны гики, которые вряд ли обучаются программированию на хабре.
+1
muslimov #
На вас не обижаюсь, даже буду рад если дадите какое-то ценное указание.

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

Например участок с конфигурационным файлом. Вместо
mod = __import__(modName)
host = getattr(mod, 'host', 'horn:intur')
user = getattr(mod, 'user', 'SYSDBA')

можно использовать простое и понятное
import settings
… settings.host…
… settings.user…

Кроме читаемости это будет соответствовать принципам «Simple is better than complex», «Namespaces are one honking great idea» и другим, включенным в The Zen of Python.

Так же, например, код:
def completeFile(self, i):
… widget = self.ui.listWidget
… text = widget.item(i).text()
… widget.item(i).setText('%s\t%s' % (text, u'готово'))
… self.ui.progressBar.setValue(0)


Читая его получаем:
функция «завершить i-й файл»:
… На форме у нас есть некоторый ListWidget, возьмем его
… Допишем к тексту i-го элемента в нем слово «готово»
… Возьмем некоторый прогрессбар на форме и установлим его прогресс в 0.


Отсюда сразу встают вопросы, что за ListWidget, что за элементы в нем хранятся, каково их назначение, и почему функция «завершить файл» вместо того, чтобы завершать этот файл, лишь констатирует факт что файл завершен.

Я не претендую на истинность своих слов, это лишь результат быстрой выкладки, но можно было бы переписать так:
def file_completed(self, file_index):
… file_item = self.ui.file_list.item(file_index)
… file_item.setText( '%s\t%s' % (file_item.text(), u'готово') )
… self.ui.current_file_progress.setValue(0)


Ваш код весьма запутан, а хорошая программа всегда имеет простой (не путать с примитивностью) код, даже если эта программа сама по себе крайне сложна.
–2
muslimov #
То что, вы указали весьма спорно:

1. В моем случае скрипт запустится при незаполненных переменных host, user. В вашем случае он упадет, __import__(modName), позволяет вынести в начало файла название скрипта настроек, у вас он вбит внутри код, что не есть хорошо.

2. Во втором случае, вы меня потрясли))
Переименовать названия виджетов — это признак большой искусности программиста.

По-моему код прозрачен, вот сущности выполняющие основные действия:

Container — (пре запуск программы, проверка настроек)
-->Wnd — (класс окошка и отрисовка виджетов)
----->MainModel — управление парсингом и загрузкой
--------->DbConn — контейнер sql, и подключение к БД.

+2
alexey_uzhva #
1. Если меняется название скрипта настроек, то имеет смысл обратиться к модулю ConfigParser и вынести настройки в ini-файл, оставив при этом в покое сам модуль конфигурационного файла.

2. Идеи правильного именования вы найдете в таких трудах, как «Совершенный код» (глава 11), «Domain driven development» и многих других.

Позволю себе привести отрывок из первой упомянутой книги:
Имя переменной нельзя выбирать, как кличку собаке:… В отличие от собаки и ее клички, которые являются разными сущностями, переменная и ее имя формируют по идее одну сущность. Поэтому и адекватность переменной во многом определяется ее именем. Выбирайте имена переменных со всей тщательностью.


Попробуйте читать код после обфускатора, если считаете что стиль и имена переменных не имеют значения.
–4
muslimov #
2. Найдите хоть одну здравую программу на питоне которая использует ini-файлы.

1. имена переменных и виджетов абсолютно разные вещи возьмите любой gui-туториал от qt, и посмотрите там имена виджетов. Это не принципиально потому что, в на форме может быть сотня виджетов и важдому присваивать имя это никому не нужная работа.
Имена переменных в моем коде все корректны.

+1
alexey_uzhva #
1. Давайте не доводить до абсурда. Если вам не нравится формат INI — сделайте в XML, YAML или любом другом формате, который вам нравится.

Суть не в конкретном формате, а в том, что есть модуль Settings, в котором есть параметры.

Откуда они там берутся — это проблема модуля settings и никакого другого модуля программы.

2. Про переменные даже отвечать не буду. Я вам привел ссылки на литературу, считаю что этого достаточно.
–4
muslimov #
абсурд — это ваши замечания, объективно почему получился код средненький, как написать лучше вы не сказали.
+8
Guria #
Статья содержит пару идеологических просчётов:
1. Исходники в cp1251. Что не самое страшное, пока код не выпускается из ваших рук.
2. Просчёт похуже: скриншоты текста в jpg

это не xkcd, не ищете текста alt
+1
Bytamine #
Какая прелесть :)
+1
Guria #
эта прелесть — регулярный гость на хабре :-)
+2
zzerf #
В принципе пост хороший, но уж больно «галопом по Европам». Пример написания гуевого приложения, работающего с БД, рассчитаный на новичковую аудиторию, имхо, лучше бы расписать подетальнее и пообширнее — и это задача не из простых. Не менее интересны и комментарии к тексту!

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