繁体   English   中英

Python内部无限循环的用户输入太慢,容易混淆

[英]Python user input inside infinite loop too slow, easily confused

我有一个在Raspberry Pi上运行的Python脚本,它等待用户输入并在SQLite数据库中记录输入:

#!/usr/bin/env python

import logging
import db

while True:
    barcode = raw_input("Scan ISBN: ")
    if ( len(barcode) > 1 ):
        logging.info("Recording scanned ISBN: " + barcode)
        print "Recording scanned ISBN: " + barcode
        db.recordScan(barcode, 1)

db.recordScan()方法如下所示:

# Adds an item to queue
def recordScan(isbn, shop_id):
    insert = "INSERT INTO scans ( isbn, shop_id ) VALUES ( ?, ? )"
    conn = connect()
    conn.cursor().execute(insert, [isbn, shop_id])
    conn.commit()
    conn.close()

(注意:整个代码库可以在https://github.com/martinjoiner/bookfetch-scanner-python/上找到,如果你想知道我是如何连接数据库的那样)

我的问题是使用USB条形码扫描仪(实际上只是一个键盘输入,发送一系列按键,然后按Enter键),输入速度非常快,命令行似乎“混乱”

例如,比较以下结果......

当你运行缓慢时,脚本运行良好,命令看起来很整洁,如下所示:

Scan ISBN: 9780465031467
Recording scanned ISBN: 9780465031467
Scan ISBN: 9780141014593
Recording scanned ISBN: 9780141014593
Scan ISBN: 

但是当你用力锤击它并且速度非常快时,输入提示会超越自身,并且脚本打印的消息会写在输入提示之上:

Recording scanned ISBN: 9780141014593
9780141014593
9780141014593
9780465031467
Recording scanned ISBN: 9780141014593
Scan ISBN: Recording scanned ISBN: 9780141014593
Scan ISBN: Recording scanned ISBN: 9780141014593
Scan ISBN: Recording scanned ISBN: 9780465031467
Scan ISBN: 9780571273188
9780141014593

它有时会无限期地挂在那个位置,我不知道它在做什么,但是你可以用另一个输入再次唤醒它并且它继续正常,尽管它挂在它之前的输入没有被记录,这是坏的因为它使整个系统不可靠。

我的问题是:这是不可避免的,我必须忍受? 我是否总是能够通过接近连续输入太多输入来超越低功耗的Raspberry Pi,或者是否有更快的方法来实现这一目标? 我可以将数据库写操作推送到另一个线程或那些行吗? 原谅我的无知,我正在学习。

不要从用户输入构建SQL字符串。 永远。

始终使用参数化查询。

# Adds an item to queue
def recordScan(isbn, shop_id):
    insert = "INSERT INTO scans ( isbn, shop_id ) VALUES ( ?, ? )"
    conn = connect()
    conn.cursor().execute(insert, [isbn, shop_id])
    conn.commit()
    conn.close()

请阅读https://docs.python.org/2/library/sqlite3.html ,至少在页面的上半部分,他们会解释这种方法。

您似乎每次都打开和关闭数据库。 这显然会增加巨大的开销,特别是当你正在“锤击”它时。
在开始时连接到数据库一次,在退出时关闭它。
在两者之间,只需执行insertupdatedelete语句。

编辑:
对于这个目的,我改名db.py被称为barcode1.py因此进行相应的编辑。 更改listen.py如下:

#!/usr/bin/env python

import logging
import barcode1
DB_FILE_NAME = "scan-queue.db"
my_db = barcode1.sqlite3.connect(DB_FILE_NAME)
my_cursor = my_db.cursor()

def InsertScan(isbn, shop_id):
    insert = "INSERT INTO scans ( isbn, shop_id ) VALUES ( ?, ? )"
    my_cursor.execute(insert, [isbn, shop_id])
    my_db.commit()

while True:
    barcode = raw_input("Scan ISBN: ")
    if ( len(barcode) > 1 ):
        logging.info("Recording scanned ISBN: " + barcode)
        print "Recording scanned ISBN: " + barcode
        InsertScan(barcode, 1)
my_db.close()

为了您的目的,将“barcode1”的引用替换为“db”
正如你所看到的,这里发生的所有事情都是添加了一个单独的函数来完成写作和写作。
很明显,这是一个快速的模拟,可以无法估量地改进,事实上我将其重写为单个脚本。 这是一个经典的例子,在尝试编写面向对象的代码时,你最终会在脚下拍摄自己。
实际上,您可以不使用该函数,只需在while语句中包含insert代码即可。

锁定:来自sqlite3文件:

 sqlite3.connect(database[, timeout, detect_types, isolation_level, check_same_thread, factory, cached_statements, uri])

打开与SQLite数据库文件数据库的连接。 您可以使用“:memory:”打开与驻留在RAM而不是磁盘上的数据库的数据库连接。

当多个连接访问数据库,并且其中一个进程修改数据库时,SQLite数据库将被锁定,直到提交该事务为止。 timeout参数指定连接在引发异常之前等待锁定消失的时间。 timeout参数的默认值为5.0(五秒)。

根据用户@tomalak,@ rolf-of-saxony和@hevlastka的有用建议进行了大量实验后,我得出的结论是,这我不得不忍受的必然性。

即使您通过删除数据库写入过程并将其作为一个简单的parrot脚本(仅重复返回输入)来将示例删除为基础知识(请参阅Raspberry Pi上的Python用户输入内部无限循环未命中输入时使用多个输入 ),它仍然是可以快速扫描项目以使输入被错过/跳过/忽略。 Raspberry Pi根本无法跟上。

因此,我现在的方法是添加音频反馈功能,例如蜂鸣声,以便在设备准备好接收下一个输入时向用户指示。 我不想下去的路线,但似乎我的代码是最有效的,我们仍然能够达到极限。 责任在于用户不会以极快的速度前进,而我们能够做到最好的负责任的产品构建者会给予他们良好的反馈。

除了我在第一个答案中提出的问题之外,还有另一个问题会影响更新的速度,即commits
您会发现,如果批量commit ,速度会呈指数级增长。 调整日记,然后重新开始。
在PI 3上工作时,我在日志开启的情况下在10秒内模拟了5000次更新,并在0.43秒内关闭了日志。
如果更改代码以将条形码存储在列表中,然后批量启动数据库更新,则代码将在Raspberry Pi上运行。

请参阅下面的测试代码:

#!/usr/bin/env python
import sqlite3
import time
DB_FILE_NAME = "scan-queue.db"
my_db = sqlite3.connect(DB_FILE_NAME)
my_cursor = my_db.cursor()
my_cursor.execute('CREATE TABLE if not exists scans(id INTEGER PRIMARY KEY AUTOINCREMENT,isbn TEXT NOT NULL,shop_id INT NOT NULL)')   
my_db.commit()
#This line turns off journaling, passing off the writes to the OS
# No rollbacks are available and corruption can occur if the machine has an issue
# but you're not NASA
my_cursor.execute("PRAGMA synchronous = OFF") #Can increase speed 20 fold
def InsertScan(isbn, shop_id):
    insert = "INSERT INTO scans ( isbn, shop_id ) VALUES ( ?, ? )"
    my_cursor.execute(insert, [isbn, shop_id])

tot_t = time.time() #Time entire run
shop_id = 1
barcode = 11111111111111
batch=[]
while shop_id < 5000:
    #barcode = raw_input("Scan ISBN: ")
    batch_cnt = 0
    while batch_cnt < 100:
        shop_id +=1
        barcode +=1
        batch_cnt +=1
        print "Recording scanned ISBN: ", barcode, shop_id
        batch.append((barcode,shop_id))
    print "Saving", str(len(batch)), "scanned ISBN's"
    t = time.time() #Time batch update
    for i in batch:
        InsertScan(i[0],i[1])
    batch=[]
    my_db.commit()
    t2 = time.time() - t
    print "Secs =", t2 #Print update time in seconds
print "Saving", str(len(batch)), "scanned ISBN's"
for i in batch: #Final update (just in case) or when program is quit
    InsertScan(i[0],i[1])
my_db.commit()
x = my_cursor.execute("select count(*) from scans")
tot_t2 = time.time() - tot_t
print "5000 Updates in ", tot_t2 #Print update time in seconds
for i in x:
    print i,"Total rows in scans table" #Print total of records in table
my_db.close() 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM