IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

PyQt Python Discussion :

[Code Review] Narrowable Filter proxy Model - Accèlerer la recherche dans une tableview (large dataset)


Sujet :

PyQt Python

  1. #1
    Membre actif
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2013
    Messages
    156
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Santé

    Informations forums :
    Inscription : Octobre 2013
    Messages : 156
    Points : 218
    Points
    218
    Par défaut [Code Review] Narrowable Filter proxy Model - Accèlerer la recherche dans une tableview (large dataset)
    Salut à tous,

    J'ai écrit un petit code, adapter de quelques exemples en c++.

    Le problème initial était que pour des grosses quantités de donnée, la rechercher dans mon QTableview est atrocement lente.
    Surtout que je souhaite permettre aux utilisateurs de chercher sur toute les colonnes plutôt que l'obliger à en sélectionner une sur laquelle il veux faire sa recherche.

    J'ai donc fait quelques recherches sur internet et j'ai sélectionner quelques approches qui me semblent pertinentes. J'ai rien trouvé en python du coup j'ai converti quelques samples en PyQt.

    Si vous avez des pistes d'améliorations, des remarques... je suis preneur, si vous avez déjà eu ce genre de problème et que vous avez utilisé une autre solution, ça m’intéresse aussi !

    Merci d'avance !

    AbstractProxyChainModel
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
     
    from enum import Enum
    from PyQt5.QtCore import QIdentityProxyModel
     
    # QStack seem to not be available in pyqt
    class QStack:
     
        def __init__(self):
            self.items = []
     
        def isEmpty(self):
            return self.items == []
     
        def push(self, item):
            self.items.append(item)
     
        def pop(self):
            return self.items.pop()
     
        def peek(self):
            return self.items[len(self.items)-1]
     
        def size(self):
            return len(self.items)
     
        def clear(self):
            self.items = []
     
        def front(self):
            return self.items[0]
     
        def top(self):
            return self.items[-1]
     
     
    class RelationType(Enum):
     
        Identical = 1
        Narrower = 2
        Different = 3
     
     
    class AbstractProxyChainModel(QIdentityProxyModel):
        """
        An abstract class representing a proxy chain. It does not know anything
        about the proxies, it just manages the chain using the "can_be_chained"
        function. It pops the stack automatically until it finds a suitable model,
        or it reaches the original source.
     
        It takes ownership over the proxies and deletes them, but not over the
        proxies, but not over the original source.
        """
     
        def __init__(self, parent=None):
            super(AbstractProxyChainModel, self).__init__(parent)
     
            # The stack of chained models.
            self.chain = QStack()  # QStack<QAbstractProxyModel>
     
            # QIdentityProxyModel's source model will point to the last filter model,
            # so we need a new variable to hold the **actual** source.
            self.real_source_model = None
     
        def clear_chain(self):
            """Removes all the proxy models and sets the original data
               as our source."""
            super().setSourceModel(self.real_source_model)
            self.chain.clear()
     
        def add_to_chain(self, model):
            """Add a new proxy model to the chain."""
             # We take ownership over the proxy model.
            model.setParent(self)
            # Pop the stack until we find a suitable proxy to use as a source for the
            # new one. If no suitable proxy, we just chain it to the original source
            # directly.
            # We initialize it in case the chain is empty.
            relation = RelationType.Different
            while not self.chain.isEmpty():
                relation = self.relationTo(model, self.chain.top())
                if RelationType.Different is not relation:
                    break
                self.chain.pop()
     
            if RelationType.Identical is relation:
                # New model is redundant, discard it and just reuse the top model.
                del model
            else:
                src_model = self.real_source_model if self.chain.isEmpty() else self.chain.top()
                model.setSourceModel(src_model)
                self.chain.push(model)
            if super().sourceModel() is not self.chain.top():
                super().setSourceModel(self.chain.top())
     
        def setSourceModel(self, sourceModel):
            """ Reimplemented because we need to cleanup the stack of filters and set
                realSourceModel.
            """
            self.real_source_model = sourceModel
     
            # QSortFilterProxyModel doesn't discard its filter on source model change, instead
            # it just reapplies it. We want to mimic that behavior. Filters have a source changed
            # event/signal, so it's enough to set the bottom-most filter in tha stack to the new
            # source model and the change will propagade all the way through to the top-most
            # filter.
            if not self.chain.isEmpty():
                self.chain.front().setSourceModel(self.real_source_model)
            else:
                super().setSourceModel(self.real_source_model)
    NarrowableFilterProxyModel
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
     
    from abstract_proxy_chain_model import AbstractProxyChainModel, RelationType
    from PyQt5.QtCore import QSortFilterProxyModel
     
     
    class NarrowableFilterProxyModel(AbstractProxyChainModel):
     
        def __init__(self, parent=None):
            super(NarrowableFilterProxyModel, self).__init__(parent)
     
        def setFilterFixedString(self, s):
            if not s:
                self.clear_chain()
            else:
                model = QSortFilterProxyModel(self)
                model.setFilterFixedString(s)
                self.add_to_chain(model)
     
        def relationTo(self, model, sourceModel):
            pattern = model.filterRegExp().pattern()
            source_pattern = sourceModel.filterRegExp().pattern()
     
            if pattern == source_pattern:
                return RelationType.Identical
            elif source_pattern in pattern:
                return RelationType.Narrower
            else:
                return RelationType.Different
    Test :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
     
    import sys
    import PyQt5
    from PyQt5 import QtWidgets
    from PyQt5 import QtCore
    from PyQt5.QtWidgets import QApplication
    from PyQt5.QtCore import qInstallMessageHandler
     
    from UI_mainwindow import Ui_MainWindow
    from narrowable_filter_proxy_model import NarrowableFilterProxyModel
     
     
    def qtMsgHandler(msg_type, msg_log_context, msg_string):
        print('file:', msg_log_context.file)
        print('function:', msg_log_context.function)
        print('line:', msg_log_context.line)
        print('  txt:', msg_string)
        msg = "File : {0}, Function : {1}, Line : {2}, Message : {3}".format(msg_log_context.file, msg_log_context.function,
                                                                             msg_log_context.line, msg_string)
        print(msg)
     
     
    qInstallMessageHandler(qtMsgHandler)
     
     
    class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
     
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
            self.setupUi(self)
            str_list = list()
     
            for i in range(300000):
                str_list.append(str(i))
     
            self.model = QtCore.QStringListModel(self)
            self.model.setStringList(str_list)
     
            self._filter = NarrowableFilterProxyModel(self)
            self.listView.setModel(self._filter)
     
            self._filter.setSourceModel(self.model)
     
            self.lineEdit.textChanged.connect(self.on_text_changed)
     
        def on_text_changed(self, text):
            _filter = self.listView.model()
            _filter.setFilterFixedString(text)
     
     
     
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
     
        # Create class
        mw = MainWindow()
        mw.show()
        err = app.exec_()
        print("return code : ", err)
        sys.exit(0)
    code UI mainwindow
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
     
     
    from PyQt5 import QtCore, QtGui, QtWidgets
     
    class Ui_MainWindow(object):
        def setupUi(self, MainWindow):
            MainWindow.setObjectName("MainWindow")
            MainWindow.resize(400, 300)
            self.centralWidget = QtWidgets.QWidget(MainWindow)
            self.centralWidget.setObjectName("centralWidget")
            self.verticalLayout = QtWidgets.QVBoxLayout(self.centralWidget)
            self.verticalLayout.setContentsMargins(11, 11, 11, 11)
            self.verticalLayout.setSpacing(6)
            self.verticalLayout.setObjectName("verticalLayout")
            self.lineEdit = QtWidgets.QLineEdit(self.centralWidget)
            self.lineEdit.setObjectName("lineEdit")
            self.verticalLayout.addWidget(self.lineEdit)
            self.listView = QtWidgets.QListView(self.centralWidget)
            self.listView.setObjectName("listView")
            self.verticalLayout.addWidget(self.listView)
            MainWindow.setCentralWidget(self.centralWidget)
            self.menuBar = QtWidgets.QMenuBar(MainWindow)
            self.menuBar.setGeometry(QtCore.QRect(0, 0, 400, 25))
            self.menuBar.setObjectName("menuBar")
            MainWindow.setMenuBar(self.menuBar)
            self.mainToolBar = QtWidgets.QToolBar(MainWindow)
            self.mainToolBar.setObjectName("mainToolBar")
            MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.mainToolBar)
            self.statusBar = QtWidgets.QStatusBar(MainWindow)
            self.statusBar.setObjectName("statusBar")
            MainWindow.setStatusBar(self.statusBar)
     
            self.retranslateUi(MainWindow)
            QtCore.QMetaObject.connectSlotsByName(MainWindow)
     
        def retranslateUi(self, MainWindow):
            _translate = QtCore.QCoreApplication.translate
            MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))

  2. #2
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    J'utilise QTableView avec une base de données sqlite, et j'avais besoin d'ajouter des fonctions de tri et de filtrage/recherche.

    j'ai alors créé une classe héritant de QSortFilterProxyModel, et je l'ai modifié/complété pour avoir ce que je voulais.

    Cette classe s'interpose entre le modèle (chez moi un QSqlRelationalTableModel) et le QTableView.

    J'ai mis ainsi à disposition de l'utilisateur:
    - filtrage par wildcard (pour une colonne ou toutes)
    - filtrage par regex (pour une colonne ou toutes)
    - filtrage par script SQL (partie derrière le WHERE d'un SELECT)
    - filtrage par recherche de mot similaire avec ratio (pour une colonne ou toutes)

    J'utilise par exemple le dernier filtrage par mots similaires pour trouver des mots ayant eu des fautes de frappe lors de la saisie au clavier.

    On peut demander à tenir compte de la casse ou pas.

    Tout cela marche très bien, et je le fais avec des tables de plusieurs milliers de lignes avec un temps de réponse satisfaisant (réponse quasi immédiate).

    Mais je ne sais pas ce que tu entends par "grosses quantités de données". A partir d'une certaine taille (plusieurs millions?) peut-être faut-il voir du côté des "big datas": il commence à y avoir des modules Python.
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

Discussions similaires

  1. generateur de code java pour rechercher dans une base de donnees
    Par cdubet dans le forum EDI et Outils pour Java
    Réponses: 5
    Dernier message: 18/02/2017, 10h10
  2. Recherche dans une chaîne des codes contenus dans une table
    Par funkyjul dans le forum Langage SQL
    Réponses: 3
    Dernier message: 21/07/2011, 08h28
  3. optimiser le code d'une recherche dans une feuille excel
    Par h_adil dans le forum Macros et VBA Excel
    Réponses: 1
    Dernier message: 21/05/2008, 21h20
  4. PB pour appliquer un modele de stratégie .adm dans une GPO.
    Par Alain18 dans le forum Windows Serveur
    Réponses: 1
    Dernier message: 10/08/2005, 16h00

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo