Python3学习最后2个主题:简单的数据库操作(MVC)、简单的多线程

       按照计划开始Python3学习最后2个主题:简单的数据库连接和多线程。目标也很简单,能够实现一些简单的任务,需要时能够完成自己的工作处理就行。至于流行的Python来做网络爬虫等估计可预见的时间内用不到,暂时不学。原来准备学习的通信,也不学了。后面会用数据库和excel配合,完成一个实操的小工具,然后Python的学习就暂时告一段落了。虽然懂得不多,但基本算入门了吧。

一、多线程

      前面用Timer实验了一个例子程序。还算比较简单。直接贴代码上来。后面再逐步完善。

import  time,datetime

import  threading,timer

def  timer_start():
    print("I'm timer1111,")
    t=threading.Timer(1,test_func,("Parameter1",))  #启动一个新线程
    t.start()
    while True:
        time.sleep(5)
        strTime = datetime.datetime.now().strftime('%Y-%M-%D  %H:%M:%S')
        print("thread main:",strTime)

def   test_func(msg1):
    print("I'm test_func,",msg1)
    while True:
        time.sleep(2)
        strTime = datetime.datetime.now().strftime('%Y-%M-%D  %H:%M:%S')
        print("thread 2",strTime)
两个线程同时守候运行。

二、使用数据库表(参考下面的一个实例进行学习)

【原创】python3+PyQt5 使用数据库表视图_basisworker_新浪博客

http://blog.sina.com.cn/s/blog_c22e36090102x3o2.html


(一) 数据库操作

2.1 建立数据库连接,打开文件:

from PyQt5.QtSql import (QSqlDatabase, QSqlQuery, QSqlRelation,
                         QSqlRelationalDelegate, QSqlRelationalTableModel, QSqlTableModel)

app = QApplication(sys.argv)

filename = os.path.join(os.path.dirname(__file__), "assets.db")
create = not QFile.exists(filename)
db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName(filename)
if not db.open():
    QMessageBox.warning(None, "Asset Manager",
                        ("Database Error: {0}"
                         .format(db.lastError().text())))
    sys.exit(1)

2.2创建数据表:

def createFakeData():
    import random

    print("Dropping tables...")
    query = QSqlQuery()
    query.exec_("DROP TABLE assets")
    query.exec_("DROP TABLE logs")
    query.exec_("DROP TABLE actions")
    query.exec_("DROP TABLE categories")
    QApplication.processEvents()

    print("Creating tables...")
    query.exec_("""CREATE TABLE actions (
                id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
                name VARCHAR(20) NOT NULL,
                description VARCHAR(40) NOT NULL)""")
    query.exec_("""CREATE TABLE categories (
                id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
                name VARCHAR(20) NOT NULL,
                description VARCHAR(40) NOT NULL)""")
    query.exec_("""CREATE TABLE assets (
                id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
                name VARCHAR(40) NOT NULL,
                categoryid INTEGER NOT NULL,
                room VARCHAR(4) NOT NULL,
                FOREIGN KEY (categoryid) REFERENCES categories)""")
    query.exec_("""CREATE TABLE logs (
                id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
                assetid INTEGER NOT NULL,
                date DATE NOT NULL,
                actionid INTEGER NOT NULL,
                FOREIGN KEY (assetid) REFERENCES assets,
                FOREIGN KEY (actionid) REFERENCES actions)""")
    QApplication.processEvents()
#填充数据
    print("Populating tables...")
    query.exec_("INSERT INTO actions (name, description) "
                "VALUES ('Acquired', 'When installed')")
    query.exec_("INSERT INTO actions (name, description) "
                "VALUES ('Broken', 'When failed and unusable')")
    query.exec_("INSERT INTO actions (name, description) "
                "VALUES ('Repaired', 'When back in service')")
    query.exec_("INSERT INTO actions (name, description) "
                "VALUES ('Routine maintenance', "
                "'When tested, refilled, etc.')")
    query.exec_("INSERT INTO categories (name, description) VALUES "
                "('Computer Equipment', "
                "'Monitors, System Units, Peripherals, etc.')")
    query.exec_("INSERT INTO categories (name, description) VALUES "
                "('Furniture', 'Chairs, Tables, Desks, etc.')")
    query.exec_("INSERT INTO categories (name, description) VALUES "
                "('Electrical Equipment', 'Non-computer electricals')")
    today = QDate.currentDate()

2.3填充数据的部分操作:使用Oracle格式的命名变量法

query.prepare("INSERT INTO assets (name, categoryid, room) "
              "VALUES (:name, :categoryid, :room)")
logQuery = QSqlQuery()
logQuery.prepare("INSERT INTO logs (assetid, date, actionid) "
                 "VALUES (:assetid, :date, :actionid)")
assetid = 1
(给name、category、room赋值)
query.bindValue(":name", name)
query.bindValue(":categoryid", category)
query.bindValue(":room", room)
query.exec_()
logQuery.bindValue(":assetid", assetid)
when = today.addDays(-random.randint(7, 1500))
# when=when.toString()
logQuery.bindValue(":date", when.toString("yyyy-MM-dd"))
logQuery.bindValue(":actionid", ACQUIRED)
logQuery.exec_()

2.4遍历数据库:

print("Assets:")
query.exec_("SELECT id, name, categoryid, room FROM assets "
            "ORDER by id")
categoryQuery = QSqlQuery()
while query.next():
    id = query.value(0)
    name = str(query.value(1))
    categoryid = query.value(2)
    room = str(query.value(3))
    categoryQuery.exec_("SELECT name FROM categories "
                        "WHERE id = {0}".format(categoryid))
    category = "{0}".format(categoryid)
    if categoryQuery.next():
        category = str(categoryQuery.value(0))
    print("{0}: {1} [{2}] {3}".format(id, name, category, room))
QApplication.processEvents()

(二)MVC机制与数据库操作

Python3学习最后2个主题:简单的数据库操作(MVC)、简单的多线程

model完成数据源的数据读写;视图view会向model请求打算显示的数据项;对于每个数据项,view都会把数据项个painter传递给委托(delegate),要求绘制该项。   相应地,如果用户开始编辑操作,view会要求delegate提供一个Editor,用户接收了编辑后,更新的数据就会回传给Model。模型中的每个数据项都可以通过唯一的QModelIndex加以识别。每个QModelIndex独有三个重要属性:行、列和父对象。列表只用到行;表格用到行、列;分层模型如树会用到全部三个属性。

一些PyQt的窗口部件,例如QListWidget、QTableWidget、QTreeWidget,都带有模型和委托的视图。可以通过简便项窗口部件方便地进行控制;QListView、QTableView、QTreeView,则需要与外部窗口部件一起工作,或者使用内置的QStringListModel、QDirModel、QSqlTableModel一起工作,这些统称为自定义模型。

2.5简便项窗口部件

QTableWiget的操作为例,很简单,就是表格操作,每个格子就是一个Item。

可参考:QTableWidget详解(样式、右键菜单、表头塌陷、多选等) - 莫水千流 - 博客园 https://www.cnblogs.com/zhoug2020/p/3789076.html

例子中,Qt UserRole就是组件角色,在复杂系统中使用的比较多,原因也很简单,系统复杂,为了组件区分方便。 在Qt中很多类是可以给他添加角色值的,比如说QComboBox中的setItemData()与QStandardItemModel中的setData()这两个函数,都是在Index位置上添加角色值。在图书的实例中和id就是hash值绑定,作为索引使用。

写入时设置:item.setData(Qt.UserRole, id(ship))
使用时首先获取,然后使用:currentTableShip()  {  return self.ships.ship(    item.data(Qt.UserRole))}
ship = self.currentTableShip()
相应的python3+PyQt5验证代码都可以在下面博客找到:
【原创】python3+PyQt5 使用三种不同的简便项窗口部件显示数据_basisworker_新浪博客http://blog.sina.com.cn/s/blog_c22e36090102x3l0.html
需要说明,实际验证结果:QTimer.singleShot(0, self.initialLoad)实际上可以去掉,直接调用self.initialLoad()就可以,否则会刚运行起来就死掉。 另外,前面哪部分就是ships.pyw的内容,不需要再去找原书的源代码了,对应要简单修改一下代码。2018/06/23

2.6自定义模型的操作方法

针对已有的QAbstractModel,要实现的方法:

1)对实现只读模型很有必要的方法 data() rowCount() columnCount() headerData()

2) 实现可编辑模型很有必要的方法 flags() setData() insertRows() removeRows()
3)增加的特定情况的扩展API的方法。

2.7自定义委托

用于只读型的委托,唯一必须重新实现的是paint()

对于可编辑型的,必须重新实现createEditor() setEditorData() commitAndCloseEditor()

        self.tableView2 = QTableView()
        tableLabel2.setBuddy(self.tableView2)
        self.tableView2.setModel(self.model)
        self.tableView2.setItemDelegate(ships.ShipDelegate(self)) #数据的展示和编辑由delegate来完成

实际工作时,只要用户确认了编辑结果,编辑器(delegate)的数据就必须回写到模型中,模型通知视图,该项内容已经改变,而显示该项的视图会要求对显示的数据进行更新。

GUI快速编程这本书中的例子比较简单,对照起来,自己刷新数据的简便项窗口部件方式觉得更好一些,有复杂逻辑的估计还是用自定义模型的MVC更好吧。如果你实现的是一个最基本的取表格数据,写到表格中显示的,那就基本只要关联设置一下,其它什么都不用做。

相应的python3+PyQt5验证代码都可以在下面博客找到:

【原创】python3+PyQt5 使用自定义委托控制数据项的展示和编辑 _basisworker_新浪博客http://blog.sina.com.cn/s/blog_c22e36090102x3m5.html

【原创】python3+PyQt5 使用自定义模型保存数据并通过不同视图形式展示数据_basisworker_新浪博客http://blog.sina.com.cn/s/blog_c22e36090102x3ly.html



2.8数据库MVC设置的示例:

def __init__(self):

        super(MainForm, self).__init__()

     #配置Model
        self.assetModel = QSqlRelationalTableModel(self) #QSqlRelationalTableModel继承自QSqlTableModel,并且对其进行了扩展,提供了对外键的支持。
        self.assetModel.setTable("assets")#关联数据表
        self.assetModel.setRelation(CATEGORYID,QSqlRelation("categories", "id", "name"))  #设置外键
        self.assetModel.setSort(ROOM, Qt.AscendingOrder)#排序
        self.assetModel.setHeaderData(ID, Qt.Horizontal,"ID")
        self.assetModel.setHeaderData(NAME, Qt.Horizontal,"Name")
        self.assetModel.setHeaderData(CATEGORYID, Qt.Horizontal,"Category")
        self.assetModel.setHeaderData(ROOM, Qt.Horizontal,"Room")
        self.assetModel.select()

    #配置View
        self.assetView = QTableView()
        self.assetView.setModel(self.assetModel)#Model
        self.assetView.setItemDelegate(AssetDelegate(self))#Controller,delegate委托
        self.assetView.setSelectionMode(QTableView.SingleSelection)#只能单选
        self.assetView.setSelectionBehavior(QTableView.SelectRows)# 设置选择行为时每次选择一行
        self.assetView.setColumnHidden(ID, True)  # don't show
        self.assetView.resizeColumnsToContents()
        assetLabel = QLabel("A&ssets")
        assetLabel.setBuddy(self.assetView)

#log的和Asset的类似
        self.logModel = QSqlRelationalTableModel(self)
        self.logModel.setTable("logs")
        self.logModel.setRelation(ACTIONID, QSqlRelation("actions", "id", "name")) 
        self.logModel.setSort(DATE, Qt.AscendingOrder)
        self.logModel.setHeaderData(DATE, Qt.Horizontal, "Date")
        self.logModel.setHeaderData(ACTIONID, Qt.Horizontal,"Action")
        self.logModel.select()


        self.logView = QTableView()
        self.logView.setModel(self.logModel)
        self.logView.setItemDelegate(LogDelegate(self))
        self.logView.setSelectionMode(QTableView.SingleSelection)
        self.logView.setSelectionBehavior(QTableView.SelectRows)
        self.logView.setColumnHidden(ID, True)
        self.logView.setColumnHidden(ASSETID, True)
        self.logView.resizeColumnsToContents()
        self.logView.horizontalHeader().setStretchLastSection(True)
        logLabel = QLabel("&Logs")
        logLabel.setBuddy(self.logView)

。。。。。。

       #binding  events with slots

       self.assetView.selectionModel().currentRowChanged.connect(self.assetChanged)  #行改变链接到self.assetChanged

。。。。。。

    def assetChanged(self, index):
        if index.isValid():
            record = self.assetModel.record(index.row())
            #通过id将asset 和log关联起来
            id = record.value("id")
            self.logModel.setFilter("assetid = {0}".format(id))
        else:
            self.logModel.setFilter("assetid = -1")
        #self.logModel.reset() # workaround for Qt <= 4.3.3/SQLite bug
        #self.logModel.beginResetModel()  
        self.logModel.select()
        
self.logView.horizontalHeader().setVisible( self.logModel.rowCount() > 0)

        if PYQT_VERSION_STR < "4.1.0":
            self.logView.setColumnHidden(ID, True)
            self.logView.setColumnHidden(ASSETID, True)
        #self.logModel.endResetModel()