Chienomi

PyQt5でウェブ 【後編】 チャレンジするということ

開発::application

  • TOP
  • Articles
  • 開発
  • PyQt5でウェブ 【後編】 チャレンジするということ

新しいチャレンジの楽しさ

制作したPyQt5のタブブラウザ

これがその制作したブラウザである。QtWebEngineView自体がChromiumなどで見慣れたもので、あまり新鮮味はないが、

実は私、ちゃんとしたGUIプログラミングも、ちゃんとしたPythonプログラミングも初めて。

GUIプログラミングは1996年にVisual Basicでやったのが初めてで、もう少しちゃんとしたGUIプログラミングとしては2002年のPerl/tkプログラミングをちょっと触って(機能的には一通り触って試したくらい)、2008年にRuby/WxWidgetをやって(サンプル触ったくらい)、2017年にRuby/GNOME, Ruby/QtQuickをサンプル程度に触った、とかそんな感じ。

実用的にはZenityやYadでGUIを作ってはいるものの、その程度の話。GtkやQtを使ってがっつりプログラミングということはなかった。

Pythonに関しては、2004年にオライリーの本(Python入門, Pythonプログラミング)を買ったものの、非常に読みづらくて、かつ知りたいことが書いてないという本だったために、半ば挫折した。 それ以降一切書いていなかったわけではなくて、とても小さいプログラムはちょいちょい書いていたものの、Pythonに関する知識の蓄積というのまは基本的になかった。

このアプリケーションの源流は2018年のauto-scrolling textにあるUnsurfの開発が最初。 これもドキュメントが見つからなくて、実は作るのは結構苦労した。

そのあと引数の扱い方やif文など、ごく基本的な部分を調べつつ改修し、Unsurfの基本的な形が出来上がった。

そして先月、2019年10月にQML/QtQuickを使った従来のUnsurfから、QtWebEngineViewを使った純粋なPyQt5のプログラムに転換したのが今回のストーリーの始まりだ。

そして、今回はPythonとQt5(PyQt5)という未知といっていいものへの挑戦となっている。

私自身は得意なのはユーティリティやツール周りのプログラミングである。 総合的にはシステムや生産性を中心に考えたもので、その応用としてウェブのプログラミングもやる、という形。 「同じような作業が多くて退屈」という理由からフロントエンド周りのプログラミングはあまり好きではない。

ただ、このところ今年情熱と時間を費やしたものが無駄になったという悔しさもあって、新しいことにチャレンジしたいという気持ちになっている。 そもそも、ここ数年で相当に実力が向上した、ということを確認できたこともあって、以前は挫けてしまったようなこと、あるいは避けてしまったようなことも今ならできるのではないか、という思いだ。

先日、わざわざLuaでコードを書き直したのもそういうことで、Luaも「関心はあって知ってはいるけどちゃんと使ってないもの」だった。Pythonも、GUIプログラミングもそんな感じだ。

練習で作ったZenity的プログラム。これは簡単
Zenity的に選択したものを標準出力へ。ずっと複雑

今回のQtWebEngineを使ったプログラムだけを制作したわけではなく、このように「本来の目的」であるZenityのようなプログラムも制作中だ。 Zenityの機能は非常に単純なものだが、プログラムとして単純かというと、そういうものもあるし、そうでないものもある。

そもそもPyQtのプログラミングは、PyQt自体がほとんどC++ライブラリに対する薄いラッパーという感じで、あまりPythonっぽくないコードになる。 定義の仕方が普通の変数になり、自分にメンバを追加するのが簡単で、コールバック関数のセットも簡単というあたり、C++よりも自然でわかりやすいコードになるのでいくらかメリットはある。

それでも、GUIプログラミングというのは非常に単調で、技術的な面よりもごく単純に慣れと作業という感じである。 慣れてしまえば、すごく淡々とした内容で非常に退屈。QtQuickを使ってもあまり変わらないだろう。 だからQtCreatorやGladeを使うというのは割と自然というか、別に手書きできる技量があるかどうかという問題とは関係なく、退屈な作業をぱぱっと終わらせる手段だと思う。

荒れた道だから楽しい

Qt関係の話を検索すると、公式ドキュメントの転載が大量にヒットし、怪しげなサイトばかりになる。 あとはStack Overflow。

GitHubなんかで実際に使っているコードがあれば嬉しいんだけれども、そういうのすらない。 ちゃんと法則というか、Qtライブラリのルールというのが把握できていれば公式ドキュメントで不足ないのだろうけれども、公式ドキュメントはコード例がないので具体的な使い方がわからない。

一方、Pythonのことを検索すると、嫌と言うほど初心者向けの記事がヒットする。 正直、今回のことで私が知りたい情報はそれでも足りるので構わないのだけど、できれば公式リファレンスが見たい。 日本語だとなお嬉しい。

Pythonで知る必要がある要素というのはあまりなかったので、ほとんどはQt関係に時間を費やした。 それも、「欲しいドキュメントがない、調べても答がない」という問題だ。

こんなにもコードがないとは思っていなくてとても困惑した。

「わからない」「どうするんだ?」ということに迷いながらで、結局答はどこにも書いてないことも多くあった。

だが、この時間が楽しい。

前述の通り、GUIプログラミング自体は単調な作業である。 出来上がったコードをみれば、「また随分とつまらないコードを書いたな」という気持ちになる。

だが、答を知ってしまえばそのとおりなのだが、「答がわからないことは本当に苦しくて楽しい」と思う。 ひたすら調べて、「こうかな?それともこうかな?」と考え、その思考を元に調べ方を変え、実際に動くか試してみる。 自分の考えが正解に至ったときにはとても気分が高揚する。

既にできることを繰り返していくことはつまらない。 知らないことに実力をぶつけていくのが楽しい。

自分が持っている戦術がない人は、なにかをできるようになろうと未知のことを一生懸命勉強するだろうけど、自分が得意とするもの、戦えるものをもっているとどうしてもその範疇でなんとかしてしまおうと考えがちになる。 そうすると、最初に感じたはずのわくわく感とかを見失ってしまう。プログラミングの楽しさとかも、なんだか曖昧なものになってしまう。

こうして自分に備わっていないもので、「こうしなさい」という道が整っていないからこそ楽しい。 挑戦するということは本質的に楽しいということを思い起こさせてくれる。

そう、挑戦は楽しい。日々緊張感を強いられていると忘れがちだけれど、

なお、今回私はコードを書くにあたって、まさに知りたいことを調べるという方法でPythonを習得したが、これは他にできるプログラミング言語があり、その言語に限らず一般的なプログラミング言語に関する概念を知っていて、なおかつPythonがどのような言語であるかを知っているためにそれを踏まえて調べているのであり、プログラミングの初学において勧められる方法ではない。

なぜPyQt5

なぜQt

昔ちょっと触った範囲では、Gtk+2とQt4だったが、Qtのほうが機能が豊富で使いやすかった。

Gtkを選択すればRuby-Gnomeを使うという選択肢が生まれるが、使いやすさからいえばQtであると感じた。

また、今回の場合はそもそもの発端としてQtWebEngineを使うというのがあり、兼ねてから「Qtのほうが好き」というのがあったので、これを機にQtを採用した。

クロスプラットフォームのGUI環境という意味でいえば、Java(Swing)を使うのが最も優れた選択肢であるが、Javaで書こうという気持ちは微塵も起きなかった。

Electronを使うという選択肢もあるが、これも言語が固定されてしまう上に、簡単に書けるGUIの構造というのが限られてしまい、希望に沿ったもの(Zenityのようなものとか)が作りづらい。

なぜPython

GtkにせよQtにせよ、C++を別とすればPythonがもっとも確実にサポートされている。

Rubyの場合、Qtのサポートは正直なところ期待できない状態だ。qtbindingsにせよqmlにせよ、もう何年も保守されていない。

Gtkを採用してRubyを使うという選択肢もあるが、ここはQtに限らず数多く存在する「使いたいライブラリがPythonにだけ存在する」というケースへの対応力を磨くため、Pythonを選択することにした。

これは別の視点で言えば、私にとってPythonは「好ましいわけではないが許容できないわけでもないもの」と捉えていたということが大きい。

Pythonの話

手に取った本が悪かったので、「Pythonってわけわかんねぇな」という気持ちになってしまったけれど、ちゃんと勉強すると別に使いにくいということは全くない。

Pythonで特に好ましいと思うのが、(JavaScriptと同じように)インスタンスがオープン構造体のようになっていて、任意のプロパティを追加することができ、また自身のメソッドは関数オブジェクトになっているということだ。

def foomethod:
    # インスタンス変数 height になる
    self.height = 100

    # メソッドを呼び出した結果ではなく、メソッドそのもの
    foofunc = self.barmethod

def barmethod:
    # ...

Ruby的に言うとこんな感じ。

def foomethod
  @hegiht = 100
  foofunc = self.method()
end

def barmethod
  # ...
end

オブジェクトがオープン構造体のようになっていることは、潜在的にインスタンス変数が共有される必要がある場合にアクセサメソッドを書かなくて良いので「いきあたりばったりに書いたときに上に戻らなくていい」というメリットがある。 メリットなのか?と言われそうだが、JavaScriptの場合はDOMオブジェクトに値を保持させるのにとても便利な特性だ。

メソッドが標準でオブジェクトであることは特にコールバックを多様する場合に重宝する。JavaScriptのイベントドリブンプログラミングや、今回のようなGUIコールバックは典型的な例だ。

Rubyではあまりメソッドオブジェクトを使うことはなく、どちらかといえば無名関数オブジェクトを多様するように設計されている。 (特にメソッド呼び出しの引数にひとつの無名関数を必要とする場合は特別扱いされている)

微妙だなと思う点がないではない。ロードする名前空間を明示しなければならないのもちょっとどうかと思うし、ハッシュに想定したキーがないと例外を吐くのもどうかと思うし、ifとかのあとの:は本当に必要なのかと思ったりもするし、int(somestr)みたいな感じなのはオブジェクト指向としていかがなものかと思ったりもするし、ばっちり不満なしってことはない。

でも、総合的に見ればちょっと注意が必要という程度の話で、「嫌だ」とは感じないレベルだ。普通に今後も使っていけると思うし、必要があればロジックをPythonで書いてもいいと思う。

Pythonで最も気になったのは、ブロック終端がないことである。 もちろんそのことはとっくに知っていたし理解もしていたが、ブロック終端がないのでインデントレベルの違いよりも空行のほうが目立ってしまい、空行が切れ目に見えるという問題に悩まされた。 結果、「切れ目がわかりやすいようにコメントを入れる」というよくわからないスタイルになってしまった。