PythonのQt(PyQt5)におけるQtCore.QAbstractItemModel.matchの調査まとめ～サンプルコードを添えて～


## 免責事項
本記事の内容を基にして不具合が発生した場合これの責任は一切持ちません。
本記事はPyside2の公式ドキュメントを参考に英語貧者が頑張って翻訳しながらコーディングしたことのまとめなため無駄等々いろんな不具合の恐れがあります。つよつよマンはぜひこの糞ザコを正しい情報で殴ってください。理解出来たら反映します。

## 環境
Python 3.7.4
PyQt5 5.15.0

## なぜやろうとした
リストの検索を、前方一致以外で、条件当てはまるものを全て表示させたかった。

## どうやったらできた
サンプルコードをどうぞ。
[このコード](https://fereria.github.io/reincarnation_tech/11_PySide/01_PySide_Basic/00_Tutorial/08_widget_sample_04/)をPyQt5用に書き直したのを基にしている。

```python

# -*- coding: utf-8 -*-

import sys
import os.path

from PyQt5 import QtCore, QtGui, QtWidgets


class UISample(QtWidgets.QDialog):

	def __init__(self, parent=None):
		super(UISample, self).__init__(parent)
		# カスタムUIを作成

		# 1.まずレイアウトひな形を決定？
		layout = QtWidgets.QVBoxLayout()

		# 2.リストの描画部分を用意
		self.listView = QtWidgets.QListView()

		# 3.レイアウトにリストの描画部分を追加
		layout.addWidget(self.listView)

		# 4.リスト項目追加入力欄を用意
		self.lineEdit = QtWidgets.QLineEdit()

		# 5.レイアウトにリスト項目追加入力欄を追加
		layout.addWidget(self.lineEdit)

		# 6.リスト項目検索入力欄の注釈ラベルを用意
		label = QtWidgets.QLabel('↓に入力された文字の列を選択')

		# 7.レイアウトに6で用意したリスト項目検索入力欄の注釈ラベルを追加
		layout.addWidget(label)

		# 8.リスト項目検索入力欄を用意
		self.lineEditB = QtWidgets.QLineEdit()

		# 9.レイアウトにリスト項目検索入力欄を追加
		layout.addWidget(self.lineEditB)

		# 10.レイアウトを用意？
		self.setLayout(layout)

		# ここまでUI作成

		# Model(リストの実体)作成
		self.model = QtCore.QStringListModel()
		self.model.setStringList(['aaa', 'bbb', 'ccc'])
		self.listView.setModel(self.model)


		# Signal-Slot作成
		self.lineEdit.returnPressed.connect(self.addList)
		self.lineEditB.textChanged.connect(self.matchSelect)

	def matchSelect(self):
		# Listから指定の文字の行を探して、見つかったら選択
		txt = self.lineEditB.text()

		if not txt:
			return

		stIndex = self.model.index(0, 0)
		searchIndex = self.model.match(
			stIndex,
			QtCore.Qt.DisplayRole,
			hits = -1,
			flags = (
				QtCore.Qt.MatchContains|
				QtCore.Qt.MatchCaseSensitive|
				QtCore.Qt.MatchWildcard
			)
		)

		print([n.data() for n in searchIndex])


	def addList(self):
		txt = self.lineEdit.text()
		strList = self.model.stringList()
		strList.append(txt)
		self.model.setStringList(strList)
		self.lineEdit.clear()


if __name__ == '__main__':
	app = QtWidgets.QApplication(sys.argv)
	a = UISample()
	a.show()
	sys.exit(app.exec_())
```
## 解説
本コードの中核は、以下の部分である

```python

stIndex = self.model.index(0, 0)
	searchIndex = self.model.match(
		stIndex,
		QtCore.Qt.DisplayRole,
		txt,
		hits = -1,
		flags = (
			QtCore.Qt.MatchContains|
			QtCore.Qt.MatchCaseSensitive|
			QtCore.Qt.MatchWildcard
		)
```
`self.model.match`以下の変数が、
macthをいじろうとした初学者にとって見慣れた3つでなく、2つ増えていることが分かるだろう。
この増えた2つについて本記事ではなるべく丁寧に解説する。

### hits
[Pyside2公式ドキュメントのQtCore.QAbstractItemModel.match](https://doc.qt.io/qtforpython-5.12/PySide2/QtCore/QAbstractItemModel.html#PySide2.QtCore.PySide2.QtCore.QAbstractItemModel.match)曰く、`hits`は

> If you want to search for all matching items, use hits = -1.
邦訳：すべての一致する項目を検索したい場合は， hits = -1 を利用します．

とのことである。そのため`―1`を指定することによって本懐(一致するものすべて出す)を遂げられるわけである。
なお、デフォルトは1のようだ。

### flags:意味と値
次に、`flags`は何を意味するのだろうか。
先ほどの曰く、
> The way the search is performed is defined by the flags given. 
邦訳：検索の実行方法は、指定されたフラグによって定義されます。

とのことだ。指定すべき値は[Pyside2公式ドキュメントの.PySide2.QtCore.Qt.MatchFlag](https://doc.qt.io/qtforpython-5.12/PySide2/QtCore/Qt.html#PySide2.QtCore.PySide2.QtCore.Qt.MatchFlag)に記載されている。
原文はリンクに参照していただくとして翻訳を掲載する。
米印で所感も載せる。

| 変数                         | 情報                                                                                                                                                                    |
|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| QtCore.Qt.MatchCaseSensitive | 大文字と小文字を区別して検索                                                                                                                                            |
| QtCore.Qt.MatchContains      | 検索語が項目のどこかに一致する|
| QtCore.Qt.MatchStartsWith    | 検索語が項目の先頭に一致する|
| QtCore.Qt.MatchEndsWith      | 検索語が項目の最後に一致する                                                                                                                                            |
| QtCore.Qt.MatchExactly       | QVariantベースのマッチングを実行<br>※事実上、文字以外のものを文字列データとして以外で探す方法？                                                                         |
| QtCore.Qt.MatchFixedString   | 文字列ベースのマッチングを実行。<br>文字列ベースの比較は、<br>MatchCaseSensitive フラグが指定されていない限り、<br>大文字小文字を区別しない。                           |
| QtCore.Qt.MatchRegExp        | 正規表現を検索語として使用し、<br>文字列ベースのマッチングを実行。                                                                                                      |
| QtCore.Qt.MatchWildcard      | 検索語としてワイルドカード`*`付きの文字列を使用して、<br>文字列ベースのマッチングを実行。|
| QtCore.Qt.MatchRecursive     | 階層全体を検索                                                                                                                                                          |
| QtCore.Qt.MatchWrap          | 回り込み検索を実行し、<br>検索がモデル内の最後の項目に到達したときに、<br>最初の項目から再び開始し、<br>すべての項目が検査されるまで継続。<br>※途中からのスキャンに有用 |

この中から相反するもの以外のいずれか1つか複数を選択し、引数とする。

優先関係の例は以下の通り。

| A               | B             | 優先関係        | 考察                           |
|-----------------|---------------|-----------------|--------------------------------|
| MatchContains   | MatchWildcard | MatchWildcard   | Wildcardの方が機能が広いから？ |
| MatchStartsWith | MatchWildcard | MatchStartsWith | 分からん。                     |
| MatchEndsWith   | MatchWildcard | MatchEndsWith   | 分からん。                     |
| MatchStartsWith | MatchEndsWith | MatchStartsWith | 後ろから入力はできないからか？ |

2個以上の条件を重ねる場合は二個づつ解決していけばよい。
たとえば、A、B、Cとあるばあい、A、Bの関係を考え、残った方とCで考える。
MatchStartsWith、MatchEndsWith、MatchWildcardで考えるならば、まず、
MatchStartsWith、MatchEndsWithを考える。表を見るとMatchStartsWithが勝つとわかる。
次に、勝ち残ったMatchStartsWithとMatchWildcardを考える。表を見るとMatchStartsWithが勝つとわかる。
こうして、MatchStartsWithが優勝し、条件として実働する。

まだ検証してないが、
もしかしたら一緒に指定するとエラーになるものもあるかもしれないし、
Pyside2に合ってPyQt5にない条件もあるかもしれない。


### frags:実際に値を指定する

例えば、ただ、検索語が項目のどこかに一致するものを選択したい場合、
`flags = QtCore.Qt.MatchContains`とする。
複数の条件を指定する場合は`|`で区切る。始端終端に`|`がある事は許可されないので、条件の間にのみに`|`を挿入すること。
（Pythonのリストみたいに全行にカンマを入れたりはできないという事だ。）

例
`flags = QtCore.Qt.MatchContains|QtCore.Qt.MatchCaseSensitive`

一行に書き並べるとそれはそれは長くなるので、サンプルコードよろしくカッコに囲んだうえで改行するとよい。
なお、`|`は前後どちらでもよいが、先ほどの注意、「始端終端には`|`はダメ」を守ろう。

例

```python

flags = (
	QtCore.Qt.MatchContains|
	QtCore.Qt.MatchCaseSensitive|
	QtCore.Qt.MatchWildcard
)
```
この場合は、ある行の条件を無効化といったことができる。
開発中挙動の調節に役立つだろう。もしくはこのような記事を作るときに。

例：Qt.MatchCaseSensitiveを無効化

```python

flags = (
	QtCore.Qt.MatchContains|
	#QtCore.Qt.MatchCaseSensitive|
	QtCore.Qt.MatchWildcard
)
```
ただし、「始端終端には｜はダメ」は変わらず守らなくてはならない。

## 終わりに
以上でやり方の説明を終わる。
免責のとおり、責任は持てないがソースは示したので何かトラブルがあった時はそっちをさかのぼってみてほしい。
改善案があれば寄せてほしい。この記事があなたのコーディングの一助になればうれしい。