PHPのバージョンアップその2

 今日も見に来てくださって、ありがとうございます。石川さんです。

 そうそう、昨日のPHPのバージョンアップ、やっぱりうまくいっていないよねぇ、と、いろいろ調べましたので、記録しておきます。

 いや~、ひどい目にあいましたよ~!

 慌てていろいろやったので、記憶に残っている限りで原因はこれかなぁ、という感じですが、ご参考になれば幸いです。

 最初に、google-fluentdがエラーを出していたのが原因なのかなぁ、と、思ってこちらを調べてみました。こちら、ログを出力するためのサービスだそうです。Googleさまがトラブルシューティングをここに書いておいてくれていました。Google Cloud MarketPlaceからのイメージを使っている場合は、Loggingエージェントが無効になっていて、そのためにエラーが出ているということがわかりました。そうそう、このWordPressのサイトを立ち上げるときに、MarketPlaceからイメージを利用させていただきまして、あっという間に構築したのでした。それで、このLoggingエージェントを有効にする方法もここに記載してくれてあったので、そのとおりに実行するとエラーが出なくなりました。
 が、、、PHPのバージョンは依然として変化ありませんでした。

 PHPのバージョン、7.0.33がまだ動作していると言われているのは、このバージョンのPHPが残っているからだよねぇ、と思ったので、思い切ってアンインストールすることにしました。なんでそんなに思い切っちゃいましたかねぇ。そうそう、こんなコマンドでした。

$ sudo apt-get remove php7.0 php7.0-cli

 そうすると、もう使わなくなったものは、このコマンドで削除できますよ~、と、言ってきたので、調子に乗って、実行してみました。

$ sudo apt autoremove

 そして、ヘルスチェックはこれでどうなったかしらと見に行ったところ、PHPのスクリプトがそのまま出力されてきました。そう、どのページに行っても、全部スクリプトなのです。動かなくなったけど、一歩前進ですね。ちょっぴり後退したようにも思えますが、やったことが影響あったということで、前進したと思いましょう。
 これは、いままで動いていたモジュールがなくなったのが原因でした。ただ最初は原因がわからないので、あちこち調べて、バージョンアップしたり、設定ファイルをいじったり、いろいろとウロウロしてしまいました。

 最終的に、効果があったのは、以下のコマンドだと思います。

$ sudo apt-get install libapache2-mod-php --reinstall   # ←これは必要なかったかも知れない
$ sudo a2enmod php7.4
$ sudo systemctl restart apache2

 最初は、単に.phpのファイルが認識できていなくて、phpが実行できないから、その設定をすればいいのかな、と考えていて、ずっとそっちの路線で調べていたのでした。この「a2enmod」というコマンド、今回初めて発見しました。どうやらapache2のモジュールを有効化するコマンドということだそうです。インストールしたけど、有効になっていなかった、というのが原因だったということなのね。

 ということで、サイトヘルス、良好になりました。

 ふ~、まだまだ知らないことがたくさんあるなぁ、と、思い知らされた事件でした。いやー、バックアップ取って実行していたから、いろいろと思い切ってやることができました。クラウドのサービス、すばらしいですね!
 ということで、これからも精進いたします。

 しばらくたって落ち着いてきたので、PHPのプロセスを確認してみると、php-fpmは7.3が動いているようでした。

$ ps ax | grep php
26168 ?        Ss     0:00 php-fpm: master process (/etc/php/7.3/fpm/php-fpm.conf)
26169 ?        S      0:00 php-fpm: pool www
26170 ?        S      0:00 php-fpm: pool www
29794 pts/0    S+     0:00 grep php
$ 

 他のは7.4になっていたので、これもバージョンアップしておこうと、以下のコマンドを実行しました。

$ sudo a2enconf php7.4-fpm

 よく見ると、上記コマンド実行時のログにこんな風に出力されていました。

NOTICE: Not enabling PHP 7.4 FPM by default.
NOTICE: To enable PHP 7.4 FPM in Apache2 do:
NOTICE: a2enmod proxy_fcgi setenvif
NOTICE: a2enconf php7.4-fpm
NOTICE: You are seeing this message because you have apache2 package installed.

 はい、ちゃんと、書いてあったのですねぇ。まだデフォルトで使えるようになってないですよ。使えるようにするには、コマンド実行してね、って。

そして、a2enconf php7.4-fpmを実行したら、次にやることがちゃんと出ていました。

Enabling conf php7.4-fpm.
To activate the new configuration, you need to run:
  systemctl reload apache2

apache2をリロードして完了です。もしかしたら、昨日のコマンド実行時にもNOTICEが出ていたのかもしれないなぁ、ということで、ログをちゃんと見てれば回避できたかもしれないトラブルでしたね。

PHPのバージョンアップ

 今日も見に来てくださってありがとうございます。石川さんです。

 このサイト、WordPressで作られています。管理画面にダッシュボードというのがあるのですけど、使っているプラグインやテーマが古くなると、更新してください、と教えてくれる優れものです。先日も更新してください、と言われましたので、しゅるっと更新しました。更新についても何か書こうと思いましたが、簡単すぎて記憶に残っていません。

 更新後、ダッシュボードを見ていて、ふと、気づきましたが、何やら問題がありそうです。

サイトヘルスステータスに「改善が必要」と出てきました。

 まったく意味が分かりませんが、5項目ほど改善が必要な項目があるそうです。見てみましょう。左下の「サイトヘルス画面」をクリックしてみます。

サイトヘルス画面

 なんと、WordPressさん、こんな機能まであるのですね!すばらしい。おすすめの改善はいいとしても、致命的な問題は捨て置けませんね。ということで、サイトのPHPのバージョンアップ、したいと思います。

 しかし、どうやるのでしょうかねぇ。ぼくのサイト、GoogleさまのGoogle Cloud Platform (GCP)のCompute Engineというサービスを利用していまして、もう一年以上前にセットアップしたので記憶が、、、薄いなぁ。確か、MarketPlaceに用意されていたWordPress用のイメージをVMのインスタンスとしてDeploy、みたいな感じだったような。当時よくわからず、セットアップしたら、Googleさまの方で勝手に最新版に更新してくれるなんてすごいよねぇ、と、勘違いしておりました。いや、自分でやんなきゃね。

 ということで、手順は以下の通り進めていきましょう。

  • まず、自分の使っているサーバーのOSを調べる。わからなければ調べ方を調べる。
  • そのOSのPHPの更新方法を調べる。
  • 更新する前にバックアップを取得。そのためにバックアップの取り方を調べる。
  • PHPを最新版にアップデートする。
  • 動作確認。正常に動作していれば、バックアップを削除して完了。
  • 正常に動作していなければ、バックアップを戻して、ふりだしに戻る。

 と、いう感じでしょうか。計画通りにうまく行くといいのですけど。

サーバーOSの確認~バックアップ

 とりあえず、GCPのダッシュボードを見てみましょう。そこからCOMPUTE ENGINEに移動して、このサイト用のVMインスタンスの情報を見てみます。ええと、OSのバージョンは特に記載されていませんでしたね。と、いうことでSSHを使ってログインしましょう。Linuxマシンですので、バージョン確認方法をGoogle先生に教えてもらいましょう。このサイトが出てきました。最初に三つのやり方が示されていましたので、簡単に一つ目を実行してみました。

prompt $ cat /etc/os-release 
PRETTY_NAME="Debian GNU/Linux 9 (stretch)"
NAME="Debian GNU/Linux"
VERSION_ID="9"
VERSION="9 (stretch)"
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
prompt $ 

 OSは、Debian 9ということですね。
 そして、次はPHPのバージョンアップですが、今のバージョンを調べるコマンドは、php -vということですので実行してみます。

prompt $ php -v
PHP 7.0.33-0+deb9u7 (cli) (built: Feb 16 2020 15:11:40) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies
    with Zend OPcache v7.0.33-0+deb9u7, Copyright (c) 1999-2017, by Zend Technologies
prompt $ 

 PHPのバージョンは、7.0.33-0+deb9u7ということだそうです。アップグレードの前にバックアップを取ろう。Google Cloud Platformは、スナップショットという機能で、バックアップできるようです。Compute Engineでスナップショットを選択して、スナップショットの作成をクリックします。

スナップショットの作成

 ソースディスクにWordPressのVMを選択して、作成クリックすれば簡単にできそうですね。ソースディスクを選ぶとソースディスクのロケーションに基づいて、勝手にロケーションが決まりました。たくさん管理している人は、ラベルを追加するようですが、ぼくはこのサイトだけしか管理していないので、そのままで「作成」をクリックします。しばらく時間がかかって、スナップショットが出来上がりました。スナップショットはバックアップ目的で、スケジューリングして定期的に実行することが推奨されているようですね。初めて知りました。最新の状態が残っていれば大丈夫、と、思っていたので深く調べてはいなかったのですが、基幹システムを運用する場合などは、必須ですね。

 1~2分ほどでしょうか、文章を書いているうちに30GBytesのバックアップ終了しました。さすがGCP、早いですねぇ!

PHPのバージョンアップ

 このページを参考に、バージョンアップを実行していきます。SSHで接続して、以下のコマンドを順番に実行していきます。

For Debian:

$ sudo apt install apt-transport-https lsb-release

$ sudo wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg # Download the signing key

$ sudo sh -c 'echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list' # Add Ondrej's repo to sources list.

$ sudo apt update

$ sudo apt-get install php7.3

はい、無事にバージョンが変更されました。

$ php -v
PHP 7.3.16-1+0~20200320.56+debian9~1.gbp370a75 (cli) (built: Mar 20 2020 14:36:13) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.16, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.16-1+0~20200320.56+debian9~1.gbp370a75, Copyright (c) 1999-2018, by Zend Technologies

 しかし、WordPressのヘルスチェックでは、まだバージョンが更新されていませんね。再起動すれば、更新されるかな。GCPのコンソール画面からリセットを実行してみます。

  おお、リセットを押したら、警告メッセージが!

 なんと恐ろしいメッセージでしょう。「停止」の方がいいのかな。キャンセルして「停止」してみます。

 恐ろしさはあんまり変わりませんねぇ。ま、仕方ありませんね。停止して、開始しましょう。無事に停止できましたので、起動しました。起動時には以下のようなメッセージが表示されます。いったい料金はいくら請求されるのでしょうか。ビビります。

 さて、ヘルスチェックは、、、あらら、変わってませんね。いや、よく見ると違います。

 コマンドを調べてみると、どうやらphp-cgiのバージョンのようです。

prompt $ php-cgi -v
PHP 7.0.33-26+0~20200320.33+debian9~1.gbp746b8e (cgi-fcgi) (built: Mar 20 2020 14:33:44)
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies
    with Zend OPcache v7.0.33-26+0~20200320.33+debian9~1.gbp746b8e, Copyright (c) 1999-2017, by Zend Technologies
prompt $ 

 と、いうことで今度はこのページを参考にしてアップデートを実行してみます。ちょっと長いですが、以下の通り実行しました。

sudo apt-get install php7.3 php7.3-cli php7.3-cgi php7.3-fpm php7.3-gd php7.3-mysql php7.3-imap php7.3-curl php7.3-intl php7.3-pspell php7.3-recode php7.3-sqlite3 php7.3-tidy php7.3-xmlrpc php7.3-xsl php7.3-zip php7.3-mbstring php7.3-soap php7.3-opcache php7.3-common php7.3-json php7.3-readline php7.3-xml

 すでにインストールされているものはスキップしてインストールできたようです。google-fluentdでエラーが起きたと言っていますが、まあ、よいでしょう。

prompt $ php-cgi -v
PHP 7.3.16-1+0~20200320.56+debian9~1.gbp370a75 (cgi-fcgi) (built: Mar 20 2020 14:36:13)
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.16, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.16-1+0~20200320.56+debian9~1.gbp370a75, Copyright (c) 1999-2018, by Zend Technologies
prompt $ 

 バージョン上がりました!しかし、ヘルスチェックのバージョンは変わっておりません。再起動しましょうかねぇ。

 と、いうことで再起動しましたが、状況が変わりません。ログを見ていると、google-fluentdというサービスかプロセスがエラーを出力していましたので、その関係でしょうか。とりあえず、今日は力尽きました。やっぱりサーバーのセットアップとかは、苦手分野だなぁ。誰か知ってる人、教えてくださーい!

PL/SQL 効率の良いプログラミング

 今日も見に来てくださって、ありがとうございます。石川さんです。

 今日は、お仕事でオラクルのPL/SQLのストアドプロシージャを作っていて、参考にしたプログラムの書き方が気に入らなかったので、ちょっとそのことを記事にしてみました。どこが気に入らなかったって、大きくは、2点です。カーソルを使ったループの書き方と、%ROWTYPEを使ったINSERT文の書き方です。

カーソルFORループ

 PL/SQLでは、データベースからSELECT文でデータを取ってきて一件ずつ処理をする、ということがよくあります。SELECT文を使うときは、明示的にか暗黙的にかいずれにせよ、カーソルというものを使っています。カーソルは、問い合わせ結果の位置を示すポインタのようなものです。暗黙的なカーソルはPL/SQLでは一行のみ結果を戻すSELECT文や、UPDATE文、DELETE文などで使われています。複数行戻すSELECT文を利用するときには、必ず明示的にカーソルを定義する必要があります。そして、データを取得するために、カーソルをOPENして、FETCHして、FETCHが終わったら、CLOSEする必要があります。

-- カーソル定義
CURSOR c IS SELECT ...;

-- 処理部分
OPEN c;
LOOP
  -- FETCH
  FETCH c INTO 変数1, 変数2, ...;
  -- 終了条件
  EXIT WHEN c%NOTFOUND;
  -- 1件ごとの処理をここに記述します。
END LOOP;
CLOSE c;

 こんな感じです。自分でカーソルをOPENして、終わったらCLOSEする、というのがセオリーです。この場合、個別の終了条件を満たす場合や、例外などの異常が発生したときにもCLOSEを忘れないようにコーディングする必要があります。OPEN、CLOSEを忘れないようにするというわずらわしさから解放してくれる記法として、カーソルFORループという書き方があります。

-- カーソル定義
CURSOR c IS SELECT ...;

-- 処理部分
FOR i IN c LOOP
  -- 1件ごとの処理をここに記述します。
  -- 項目のデータは、「i.カラム名」という風に記載できます。
END LOOP;

 この記法、OPEN、FETCH、CLOSE、終了条件を記載する必要がありません。あ、あと、取得してきたデータを変数に代入する処理も、その変数の定義も記述する必要がありません。とってもエレガントですね。特に理由がない限り、カーソルFORループを使いましょう。ぼくは、1件だけデータを取得するSQLが何度も呼ばれるときにだけ、OPEN、FETCH、CLOSEを利用しています。通常のSELECT文は、データが1件しかなくても2件目を探しに行って、見つからないことがわかって初めてループを終了できるので、若干ですが、処理に時間がかかってしまいます。暗黙カーソルのSELECTの場合もTOO_MANY_ROWS例外を発生させないことを確認するために2件目がないことを確認しています。通常は処理時間はわずかですので、ほとんど気にする必要はありません。ただ、大量データを扱う場合など、パフォーマンスが気になるときには考慮してもよいかもしれませんね。

%ROWTYPEを利用したINSERT文

 オラクルにはテーブル定義を再利用するための記法として、%ROWTYPEという記法があります。宣言部分で以下のように記述します。これだけで、myRecordという変数の中にテーブル定義と同じ項目を宣言できます。

myRecord myTable%ROWTYPE; -- myTableテーブルの項目と同じ項目を持つ変数を定義

 このmyRecord変数を利用して、以下のようにINSERT文を記述することができます。

INSERT INTO myTable VALUES myRecord;

 この記述方法を知らないと、以下のように項目の数の2倍の記述が必要になってしまいます。

INSERT INTO myTable(
  COLUMN_NAME1,
  COLUMN_NAME2,
…
  COLUMN_NAMEn
)VALUES(
  myRecord.COLUMN_NAME1,
  myRecord.COLUMN_NAME2,
...
  myRecord.COLUMN_NAMEn
);

 ちなみに、このmyRecordの初期化は変数にNULLを代入するだけで完了です。すべての項目がNULLにセットされます。

myRecord := NULL;

 テーブルへデータをINSERTするときは、%ROWTYPEを使いましょう。%ROWTYPEを使うことで、項目の増減があっても既存部分はほぼ問題なく動作します。%ROWTYPEを使わなければ、すべての項目を再定義する必要があるため、コーディングの効率がずいぶんと悪くなります。

まとめ

 PL/SQLでデータを取得して繰り返し処理をするときは、カーソルFORループを使う、テーブルへデータをINSERTするときは、%ROWTYPEでテーブル定義を参照した変数を利用する。これだけで、ずいぶんとコーディングの時間が短縮できると思います。

Python GUI プログラミングを教えています「その3」

 今日も見に来てくださって、ありがとうございます。石川さんです。

 Python GUIプログラミング教室、先日はウィンドウを出して、サイズを変更して、背景色を変更するところまでやりました。次は、どうしようかなぁ、と、思っていましたが、やっぱりゲームなら、キャンバスが使えないとダメでしょう。と、いうことで、Canvasを作ってみましょう。

 とりあえず、ウィンドウを出すテンプレートとして、以下のスクリプトを覚えてもらいました。

import tkinter as tk

class Application(tk.Tk):
    def __init__(self):
        super().__init__()
# ここに色々な設定を書いていきます。
        
        self.mainloop()

if __name__ == "__main__":
    Application()

 サンプルとして、キャンバスを作ってみましょう。

        self.canvas = tk.Canvas()
        self.canvas.pack()

 追加しましたが、大きさが若干変化したくらいで、あんまり変わりませんね。次に線を引いてみましょう。

        self.canvas.create_line(10,10,200,200)
線が引けました

 キャンバスは、左上に原点の(0,0)があって、x軸が右、y軸が下に行くほど数値が増えていく構造です。なので上記の一行は、左上の(10,10)の座標から右下の(200,200)へ線を引く、という命令になります。ここまで教えて、さあ、自分でも好きな線を引いてみて、と、促したところ、下の二行を追加してくれました。

        self.canvas.create_line(100,100,200,200)
        self.canvas.create_line(65,65,150,150)

 当然ながら、実行しても線は増えませんね。追加した線は、もともと引いてあった線の上を引き直しているだけですからねぇ。

 次に、fill=”white”とか”red”で色が付けられることと、create_rectangleで四角形、create_ovalで楕円が書けることを教えてあげて、遊んでもらいました。最終的に何かができた、と、言っていたのですが、出来上がりは、こんな感じでした!

日の丸!

 そして、そこで終わらずに、さらに欲張って、キーボードを押されたイベントで押されたキーを出力するスクリプトを追加しました。

import tkinter as tk

class Application(tk.Tk):
    def __init__(self):
        super().__init__()

        self.canvas = tk.Canvas()
        self.canvas.pack()
        self.canvas.create_rectangle(10,50,200,160,fill="white")
        self.canvas.create_oval(80,80,130,130,fill="red")

        self.bind("<KeyPress>",self.print_key)
        
        self.mainloop()

    def print_key(self, event):
        print(event.keysym)
        
if __name__ == "__main__":
    Application()

12行目のbind"<KeyPress>"イベントに、16行目のself.print_keyメソッドを割り当てています。つまり、何かのキーが押されたよ、というイベントが発生したら、self.print_keyメソッドが実行されるようになります。print_keyの中で、event.keysymprintしています。押されたキーの内容によって、event.keysymの内容が変化します。適当にキーを押すと、出力結果は以下のようになりました。

space
a
b
c
Return
BackSpace
F1
F2
Down
Left
Up
Right
1
2
3
4
Shift_L
exclam

 この結果から、矢印キーが押されたときに何かさせようとすると、「Down」「Left」「Up」「Right」を判定すればうまくいきそうです。

 さあ、次は、、、と、思っていたら、もう疲れた~、と、またしても逃げられてしまいました。ねぇ、ホントはプログラミングしたくないんじゃないの?
 む~、それか、教え方がよくないのかなぁ。。。

Raspberry PI が故障してしまいました(涙)

 今日も見に来てくださって、ありがとうございます。昨日からコロナウィルの影響で勤務時間が1時間ほど短くなりました。

 タイトルにありますように、先日アップデートしたRaspberry PIが故障してしまいました。3日ほど電源を入れっぱなしにしていたのがいけないのでしょうか、電源ランプが光ってはいるのですが、画面が暗いままになってしまいました。

 これは、と、思い、電源コードを抜いて、挿し直しました。(Raspberry PIは電源ボタンがありませんので。)しばらくすると、虹色の画面がでましたが、その後は待てど暮らせど画面に変化がありません。そういえば、久しぶりに電源入れたときもこんな感じだったなぁ、ということを思い出しました。その時は何度か挿し直したら動き始めたのですが、今回は何度やってもいっこうによくなる気配がありません。

 チーン。ご臨終ですね。

 せっかく息子にプログラミングを教えていたのに、しばらく中止です。環境を作り直さないとなぁ。当時作っていたSDカードがあったので、何気にそちらを挿し込んで電源を入れたところ、無事に立ち上がりましたが、、、Bluetoothのマウスが動きません。先日の手順にのっとって、認識してから、sudo apt-get updateを実行。しかし、、、Wifiの設定がされていませんでした。インターネットにつながってなかったのね。設定確認したところ、故障する前に使っていたルータ用の設定でした。む~、となりながら、しばらくWifiの設定と格闘しましたが、うまく動きません。ちゃんと動くところまでやっとけよ、自分、と、思いましたが、その当時はちゃんと動いたのでしょうねぇ。と、いうことで、やっぱりえらいぞ、自分、と思い直しました。

 仕方ないので、SDカードを再度作り直すところからやろうか、そもそも今の環境でWifi設定できないならSDカードを再作成したところで無駄かもしれない、と、ぐるぐる回っているところです。前回は、いったいどうやって設定したのでしょうか。まったく記憶がありません。もうちょっとがんばります。

 と、いうことで気を取り直してWifiの設定、がんばりました。いろいろなサイトで/etc/wpa_supplicant/wpa_supplicant.confファイルに設定を記述せよ、と書いてあったので、がんばって記述していたのですが、それが間違いでした。最終的にやったことは、このファイルの中の以下の部分をコメントアウトして、再起動です。ちなみにコメントアウトは、「#」記号を先頭に記載しました。

network={
         ssid="..."
         psk="...."
         key_msmt=WPA-PSK
}

 すると、再起動後、Wifiの設定がされていないのでスキャンします、という感じで我が家の無線LANのssidを表示してきましたので、そいつを選んで、暗号化キーを入力すれば、無事に設定完了いたしました。ホッ。

Python tkinter 事前定義の色名 Windows編

 今日も見に来てくださって、ありがとうございます。

 今回は、tkinterの事前定義の色名にはどんなものがあるのか気になったので作りました。もちろんTcl/Tk本家のホームページのこちらを参考にさせていただきました。実行すると、以下のようなウィンドウが出力されます。

tkinter Windowsでの事前定義の色名

 ソースコードは以下の通りです。

import tkinter as tk

COLORS = [
"system3dDarkShadow", "systemHighlight", "system3dLight", "systemHighlightText", "systemActiveBorder", "systemInactiveBorder", "systemActiveCaption", "systemInactiveCaption", "systemAppWorkspace", "systemInactiveCaptionText",
"systemBackground", "systemInfoBackground", "systemButtonFace", "systemInfoText", "systemButtonHighlight", "systemMenu", "systemButtonShadow", "systemMenuText", "systemButtonText", "systemScrollbar",
"systemCaptionText", "systemWindow", "systemDisabledText", "systemWindowFrame", "systemGrayText", "systemWindowText",
] 

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        for i, c in enumerate(COLORS):
            foreground = "SystemButtonText"
            if self.winfo_rgb(c) <= (10,10,10):
                foreground = "white"
            label = tk.Label(self, text=c, background=c, foreground=foreground)
            label.bind("<Enter>", self.show_color_info)
            label.grid(row=i//5, column=i%5, sticky=(tk.W+tk.E), padx=1, pady=1)
            
    def show_color_info(self, event):
        color = event.widget.cget("text")
        rgb = event.widget.winfo_rgb(color)
        rgbstring = "#%02X%02X%02X"%(rgb[0]//256,rgb[1]//256,rgb[2]//256)
        self.title("You are pointing ["+color+"] and background = "+rgbstring+"]")

if __name__ == "__main__":
    app = App()
    app.mainloop()

 COLORSの内容以外、前回とほとんど変わりませんね。数が減ったので、幅の指定をなくしたのと、色が暗いときには白い字で色名が出力されるようにしました。Mac用の色名も多数あったのでスクリプトを作ったのですけど、ぼくはMacを持っていなくて実行できなかったので、お蔵入りとなりました。

Python tkinter 事前定義の色について

 今日も見に来てくださってありがとうございます。こんな時期なのに、今日はなんと雪が降りましたね。

 先日、tkinterの事前定義の色ってどんなのがあるのかなぁ、と、思って本家のホームページまでたどり着きましたが、どんな色かわからないので、一覧を作ってみようと思いました。なんと、OS依存しない名称は760色、多いなぁ。20×38でちょっと作ってみよう。

 出来上がりイメージは以下の通りです。

上記の画面を出力するためのスクリプトは以下の通りです。

import tkinter as tk

COLORS=[
"alice blue", "AliceBlue", "antique white", "AntiqueWhite", "AntiqueWhite1", "AntiqueWhite2", "AntiqueWhite3", "AntiqueWhite4", "aqua", "aquamarine",
"aquamarine1", "aquamarine2", "aquamarine3", "aquamarine4", "azure", "azure1", "azure2", "azure3", "azure4", "beige",
"bisque", "bisque1", "bisque2", "bisque3", "bisque4", "black", "blanched almond", "BlanchedAlmond", "blue", "blue violet",
"blue1", "blue2", "blue3", "blue4", "BlueViolet", "brown", "brown1", "brown2", "brown3", "brown4",
"burlywood", "burlywood1", "burlywood2", "burlywood3", "burlywood4", "cadet blue", "CadetBlue", "CadetBlue1", "CadetBlue2", "CadetBlue3",
"CadetBlue4", "chartreuse", "chartreuse1", "chartreuse2", "chartreuse3", "chartreuse4", "chocolate", "chocolate1", "chocolate2", "chocolate3",
"chocolate4", "coral", "coral1", "coral2", "coral3", "coral4", "cornflower blue", "CornflowerBlue", "cornsilk", "cornsilk1",
"cornsilk2", "cornsilk3", "cornsilk4", "crimson", "cyan", "cyan1", "cyan2", "cyan3", "cyan4", "dark blue",
"dark cyan", "dark goldenrod", "dark gray", "dark green", "dark grey", "dark khaki", "dark magenta", "dark olive green", "dark orange", "dark orchid",
"dark red", "dark salmon", "dark sea green", "dark slate blue", "dark slate gray", "dark slate grey", "dark turquoise", "dark violet", "DarkBlue", "DarkCyan",
"DarkGoldenrod", "DarkGoldenrod1", "DarkGoldenrod2", "DarkGoldenrod3", "DarkGoldenrod4", "DarkGray", "DarkGreen", "DarkGrey", "DarkKhaki", "DarkMagenta",
"DarkOliveGreen", "DarkOliveGreen1", "DarkOliveGreen2", "DarkOliveGreen3", "DarkOliveGreen4", "DarkOrange", "DarkOrange1", "DarkOrange2", "DarkOrange3", "DarkOrange4",
"DarkOrchid", "DarkOrchid1", "DarkOrchid2", "DarkOrchid3", "DarkOrchid4", "DarkRed", "DarkSalmon", "DarkSeaGreen", "DarkSeaGreen1", "DarkSeaGreen2",
"DarkSeaGreen3", "DarkSeaGreen4", "DarkSlateBlue", "DarkSlateGray", "DarkSlateGray1", "DarkSlateGray2", "DarkSlateGray3", "DarkSlateGray4", "DarkSlateGrey", "DarkTurquoise",
"DarkViolet", "deep pink", "deep sky blue", "DeepPink", "DeepPink1", "DeepPink2", "DeepPink3", "DeepPink4", "DeepSkyBlue", "DeepSkyBlue1",
"DeepSkyBlue2", "DeepSkyBlue3", "DeepSkyBlue4", "dim gray", "dim grey", "DimGray", "DimGrey", "dodger blue", "DodgerBlue", "DodgerBlue1",
"DodgerBlue2", "DodgerBlue3", "DodgerBlue4", "firebrick", "firebrick1", "firebrick2", "firebrick3", "firebrick4", "floral white", "FloralWhite",
"forest green", "ForestGreen", "fuchsia", "gainsboro", "ghost white", "GhostWhite", "gold", "gold1", "gold2", "gold3",
"gold4", "goldenrod", "goldenrod1", "goldenrod2", "goldenrod3", "goldenrod4", "gray", "gray0", "gray1", "gray2",
"gray3", "gray4", "gray5", "gray6", "gray7", "gray8", "gray9", "gray10", "gray11", "gray12",
"gray13", "gray14", "gray15", "gray16", "gray17", "gray18", "gray19", "gray20", "gray21", "gray22",
"gray23", "gray24", "gray25", "gray26", "gray27", "gray28", "gray29", "gray30", "gray31", "gray32",
"gray33", "gray34", "gray35", "gray36", "gray37", "gray38", "gray39", "gray40", "gray41", "gray42",
"gray43", "gray44", "gray45", "gray46", "gray47", "gray48", "gray49", "gray50", "gray51", "gray52",
"gray53", "gray54", "gray55", "gray56", "gray57", "gray58", "gray59", "gray60", "gray61", "gray62",
"gray63", "gray64", "gray65", "gray66", "gray67", "gray68", "gray69", "gray70", "gray71", "gray72",
"gray73", "gray74", "gray75", "gray76", "gray77", "gray78", "gray79", "gray80", "gray81", "gray82",
"gray83", "gray84", "gray85", "gray86", "gray87", "gray88", "gray89", "gray90", "gray91", "gray92",
"gray93", "gray94", "gray95", "gray96", "gray97", "gray98", "gray99", "gray100", "green", "green yellow",
"green1", "green2", "green3", "green4", "GreenYellow", "grey", "grey0", "grey1", "grey2", "grey3",
"grey4", "grey5", "grey6", "grey7", "grey8", "grey9", "grey10", "grey11", "grey12", "grey13",
"grey14", "grey15", "grey16", "grey17", "grey18", "grey19", "grey20", "grey21", "grey22", "grey23",
"grey24", "grey25", "grey26", "grey27", "grey28", "grey29", "grey30", "grey31", "grey32", "grey33",
"grey34", "grey35", "grey36", "grey37", "grey38", "grey39", "grey40", "grey41", "grey42", "grey43",
"grey44", "grey45", "grey46", "grey47", "grey48", "grey49", "grey50", "grey51", "grey52", "grey53",
"grey54", "grey55", "grey56", "grey57", "grey58", "grey59", "grey60", "grey61", "grey62", "grey63",
"grey64", "grey65", "grey66", "grey67", "grey68", "grey69", "grey70", "grey71", "grey72", "grey73",
"grey74", "grey75", "grey76", "grey77", "grey78", "grey79", "grey80", "grey81", "grey82", "grey83",
"grey84", "grey85", "grey86", "grey87", "grey88", "grey89", "grey90", "grey91", "grey92", "grey93",
"grey94", "grey95", "grey96", "grey97", "grey98", "grey99", "grey100", "honeydew", "honeydew1", "honeydew2",
"honeydew3", "honeydew4", "hot pink", "HotPink", "HotPink1", "HotPink2", "HotPink3", "HotPink4", "indian red", "IndianRed",
"IndianRed1", "IndianRed2", "IndianRed3", "IndianRed4", "indigo", "ivory", "ivory1", "ivory2", "ivory3", "ivory4",
"khaki", "khaki1", "khaki2", "khaki3", "khaki4", "lavender", "lavender blush", "LavenderBlush", "LavenderBlush1", "LavenderBlush2",
"LavenderBlush3", "LavenderBlush4", "lawn green", "LawnGreen", "lemon chiffon", "LemonChiffon", "LemonChiffon1", "LemonChiffon2", "LemonChiffon3", "LemonChiffon4",
"light blue", "light coral", "light cyan", "light goldenrod", "light goldenrod yellow", "light gray", "light green", "light grey", "light pink", "light salmon",
"light sea green", "light sky blue", "light slate blue", "light slate gray", "light slate grey", "light steel blue", "light yellow", "LightBlue", "LightBlue1", "LightBlue2",
"LightBlue3", "LightBlue4", "LightCoral", "LightCyan", "LightCyan1", "LightCyan2", "LightCyan3", "LightCyan4", "LightGoldenrod", "LightGoldenrod1",
"LightGoldenrod2", "LightGoldenrod3", "LightGoldenrod4", "LightGoldenrodYellow", "LightGray", "LightGreen", "LightGrey", "LightPink", "LightPink1", "LightPink2",
"LightPink3", "LightPink4", "LightSalmon", "LightSalmon1", "LightSalmon2", "LightSalmon3", "LightSalmon4", "LightSeaGreen", "LightSkyBlue", "LightSkyBlue1",
"LightSkyBlue2", "LightSkyBlue3", "LightSkyBlue4", "LightSlateBlue", "LightSlateGray", "LightSlateGrey", "LightSteelBlue", "LightSteelBlue1", "LightSteelBlue2", "LightSteelBlue3",
"LightSteelBlue4", "LightYellow", "LightYellow1", "LightYellow2", "LightYellow3", "LightYellow4", "lime", "lime green", "LimeGreen", "linen",
"magenta", "magenta1", "magenta2", "magenta3", "magenta4", "maroon", "maroon1", "maroon2", "maroon3", "maroon4",
"medium aquamarine", "medium blue", "medium orchid", "medium purple", "medium sea green", "medium slate blue", "medium spring green", "medium turquoise", "medium violet red", "MediumAquamarine",
"MediumBlue", "MediumOrchid", "MediumOrchid1", "MediumOrchid2", "MediumOrchid3", "MediumOrchid4", "MediumPurple", "MediumPurple1", "MediumPurple2", "MediumPurple3",
"MediumPurple4", "MediumSeaGreen", "MediumSlateBlue", "MediumSpringGreen", "MediumTurquoise", "MediumVioletRed", "midnight blue", "MidnightBlue", "mint cream", "MintCream",
"misty rose", "MistyRose", "MistyRose1", "MistyRose2", "MistyRose3", "MistyRose4", "moccasin", "navajo white", "NavajoWhite", "NavajoWhite1",
"NavajoWhite2", "NavajoWhite3", "NavajoWhite4", "navy", "navy blue", "NavyBlue", "old lace", "OldLace", "olive", "olive drab",
"OliveDrab", "OliveDrab1", "OliveDrab2", "OliveDrab3", "OliveDrab4", "orange", "orange red", "orange1", "orange2", "orange3",
"orange4", "OrangeRed", "OrangeRed1", "OrangeRed2", "OrangeRed3", "OrangeRed4", "orchid", "orchid1", "orchid2", "orchid3",
"orchid4", "pale goldenrod", "pale green", "pale turquoise", "pale violet red", "PaleGoldenrod", "PaleGreen", "PaleGreen1", "PaleGreen2", "PaleGreen3",
"PaleGreen4", "PaleTurquoise", "PaleTurquoise1", "PaleTurquoise2", "PaleTurquoise3", "PaleTurquoise4", "PaleVioletRed", "PaleVioletRed1", "PaleVioletRed2", "PaleVioletRed3",
"PaleVioletRed4", "papaya whip", "PapayaWhip", "peach puff", "PeachPuff", "PeachPuff1", "PeachPuff2", "PeachPuff3", "PeachPuff4", "peru",
"pink", "pink1", "pink2", "pink3", "pink4", "plum", "plum1", "plum2", "plum3", "plum4",
"powder blue", "PowderBlue", "purple", "purple1", "purple2", "purple3", "purple4", "red", "red1", "red2",
"red3", "red4", "rosy brown", "RosyBrown", "RosyBrown1", "RosyBrown2", "RosyBrown3", "RosyBrown4", "royal blue", "RoyalBlue",
"RoyalBlue1", "RoyalBlue2", "RoyalBlue3", "RoyalBlue4", "saddle brown", "SaddleBrown", "salmon", "salmon1", "salmon2", "salmon3",
"salmon4", "sandy brown", "SandyBrown", "sea green", "SeaGreen", "SeaGreen1", "SeaGreen2", "SeaGreen3", "SeaGreen4", "seashell",
"seashell1", "seashell2", "seashell3", "seashell4", "sienna", "sienna1", "sienna2", "sienna3", "sienna4", "silver",
"sky blue", "SkyBlue", "SkyBlue1", "SkyBlue2", "SkyBlue3", "SkyBlue4", "slate blue", "slate gray", "slate grey", "SlateBlue",
"SlateBlue1", "SlateBlue2", "SlateBlue3", "SlateBlue4", "SlateGray", "SlateGray1", "SlateGray2", "SlateGray3", "SlateGray4", "SlateGrey",
"snow", "snow1", "snow2", "snow3", "snow4", "spring green", "SpringGreen", "SpringGreen1", "SpringGreen2", "SpringGreen3",
"SpringGreen4", "steel blue", "SteelBlue", "SteelBlue1", "SteelBlue2", "SteelBlue3", "SteelBlue4", "tan", "tan1", "tan2",
"tan3", "tan4", "teal", "thistle", "thistle1", "thistle2", "thistle3", "thistle4", "tomato", "tomato1",
"tomato2", "tomato3", "tomato4", "turquoise", "turquoise1", "turquoise2", "turquoise3", "turquoise4", "violet", "violet red",
"VioletRed", "VioletRed1", "VioletRed2", "VioletRed3", "VioletRed4", "wheat", "wheat1", "wheat2", "wheat3", "wheat4",
"white", "white smoke", "WhiteSmoke", "yellow", "yellow green", "yellow1", "yellow2", "yellow3", "yellow4", "YellowGreen",
]

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        for i, c in enumerate(COLORS):
            label=tk.Label(self,text=c,background=c,width=8)
            label.bind("<Enter>",self.show_color_name)
            label.grid(row=i//20,column=i%20)

    def show_color_name(self,event):
        color = event.widget.cget("text")
        rgb = event.widget.winfo_rgb(color)
        rgbstring = "#%02X%02X%02X"%(rgb[0]//256, rgb[1]//256, rgb[2]//256)
        self.title("You are pointing ["+color+"] and background color = ["+rgbstring+"].")

if __name__ == "__main__":
    app = App()
    app.mainloop()

 前半部分は、COLORSの定義です。Tcl/Tkの本家のホームページにあった色定義をコピペして作りました。ただ、実行時に2件、エラーが出ました。

TclError: unknown color name "agua"
TclError: unknown color name "crymson"

 きっと単なるtypoですね。以下の通り修正しました。

  • "agua" → "aqua"
  • "crymson" → "crimson"

 本家に修正依頼を出せればよいのでしょうけど、、、どこへ連絡すればよいのか分かりませんねぇ。と、いうことでこのtypoは放置するしかなさそうですね。

 プログラムの方は、ラベルをつくって、マウスポインタがラベルに入ったら(<Enter>イベントで)、ラベルに指定された色名と、RGB形式に変更した値をタイトルとして出力するように作りました。

 いつも気になるのが、mainloop()を書く位置なんだけど、スクリプトとして実行された後に書くか、__init__の最後で書くか、どっちがいいのかなぁ。たぶんどっちでもいいんだろうけど、メインアプリケーションとして実行されるなら、__init__の最後に書いておくのが正解なんじゃないかなぁ、と、思う今日この頃です。

Python GUI プログラミングを教えています「その2」

 今日も見に来てくださってありがとうございます。みなさん、新型コロナウィルスで自粛中でしょうか。

 前回のプログラミングを教えるの続きです。ちなみに息子に使わせている環境ですが、ぼくの環境は触らせたくなかったので、しばらく使っていなかったRaspberryPI3を使うことにしました。フツーにRaspbianが入っているのですけど、プログラミング教育を意識しているのか、メニューの中に「プログラミング」があって、その中からSpyder3を使うことにしました。

 あ、ちなみに、前回の内容の感想を聞いたら、「難しい」と、言っていました。うん、まあ簡単じゃないよねぇ。でもprint("Hello World!")じゃつまんないでしょ。いきなり難しいことをやらせすぎ、と、言われることもありましたが、ぼくは習うより慣れろ、が正解じゃないかなぁ、と、思っています。

 と、いうことで、次回は、ウィンドウのサイズを変えてみたり、背景の色を変えてみたりしましょう。まずは、ウィンドウのサイズを変えてみます。タイトルをセットしている次の行に追加して実行みましょう。

        self.geometry("300x100")

 これで、大きさが変わります。自由に変更してみて、というと、どこまで大きくなるんだろうかと、”10000×10000″をセットしてました。どうなるのかとハラハラしてみていましたが、スクリーンより大きいウィンドウにはならないようで、ホッとしました。

 背景色は、以下のとおりにセットすると「赤」になります。

        self.configure(background="#FF0000")

 この"#FF0000"は色の三原色で、RGBのRed、Green、Blueの順番にそれぞれ2桁の16進数で指定します。ここで16進数の説明が必要になりますね。このように三原色で指定する以外では、”red”、”green”、”blue”、”gray”など、事前に定義された色名があります。詳しくはTcl/Tkの本家のホームページに記載があります。

 Pythonでは、16進数への変換は簡単です。例えばAliceBlueとして定義されていた色は、それぞれ240、248、255ですが、”#{:02X}{:02X}{:02X}”.format(240,248,255)と指定するだけで16進数に変換されます。こんな感じです。やさしい青色ですねぇ。

        self.configure(background="#{:02X}{:02X}{:02X}".format(240,248,255))
#違う書き方もありました
        self.configure(background="#%02X%02X%02X"%(240,248,255))

 コンピュータで、どうして16進数が使われているのかという話を始めたら、「疲れた~」と、言って逃げられちゃいました。はい、16進数は余分でしたね。

 次は何やろうかなぁ。

Python GUI プログラミングを教えています「その1」

 今日も見に来てくださって、ありがとうございます。

 中学生の息子にこれからプログラミングを教えようと思います。彼は「ミニゲームをつくりたいんだよね。」と言っていましたので、最終的には自分でミニゲームができるくらいのレベル感に仕上げましょう。ちょうどtkinterの勉強をしていたので、これを使って教えようかと。さあ、どうなることでしょうね。

 プログラミングを教えるということについてちょっと考えてみました。最初のハードルとして、プログラミングってどういうことか、ということを教えるのが異様に難しいと思います。次に環境について理解してもらうこと。これも難しいです。なので、まずは実際にどうすればどうなるを体験してもらって、それに慣れてもらおうと思います。そして、そのあとからナニをやっていたのか、プログラミングとはどういうことなのか、環境にはどんなものがあるのか、ということを教えていこうかな、と、思います。

 導入としては、やっぱりおなじみの「Hello World!」ですよねぇ。と、いうことで、まずは、ウィンドウを表示するプログラムを作ってもらいます。環境はこちらで準備しましょう。コードを入力するところかやってもらって、すぐに実行する、ということを体験してもらおう。まずは以下のスクリプトからのスタートです。これを実行してみたら、どんな反応がありますかねぇ。。。

import tkinter as tk

class Application(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Hello World!")
        self.mainloop()

if __name__ == "__main__":
    app = Application()

 こんな感じです。

Tkinter版のHello World!

 はい、ちょっと難しいかなぁ、とも思いましたが、無事実行できました。「日本語でもできるの?」と聞いてきたので、「Hello World!のところを日本語で書き換えればいいよ」と教えました。すると、なんと、「こんにちは みつのり」に書き換えたスクリプトを今のスクリプトの下に書き始めて実行してしまいました。当然、ウィンドウが2回表示されました。2回目のウィンドウが下の通りです。

        self.title("こんにちは みつのり")
こんにちは みつのり

 なかなか、自由な感じですね。

 次は、何をしようかなぁ。

Raspberry PI 3 Bluetoothマウスを認識させる

 今日も見に来てくださってありがとうございます。

 久しぶりにRaspberry PI3を使ってみようと電源を入れてみました。ずいぶんと放置していたので、以前使っていたマウスがなくなって、Bluetoothマウスしか手元になかったのですよねぇ。Bluetoothを使ってペアリングする画面を開くためにマウスがないとクリックできない、というジレンマに陥りました。ということで、ターミナルからBluetoothマウスを認識させる方法を調べて動作させたので、記録しておきます。

 やったことは、以下の通り。まずはここのページを参考にさせていただきました。

  • メニュー → アクセサリ → LXTerminalを起動
  • 「sudo bluetoothctl」コマンド実行
  • [bluetooth]プロンプトが表示されるので、「agent on」を実行
  • さらに「scan on」を実行
  • 一覧に使おうと思っていた「ELECOM Laser Mouse」が登場
  • 「pair xx:xx:xx:xx:xx」を実行 (xxのところは表示されたデバイスのIDです)
  • ダイアログ画面でなにやら2度ほど聞いてきたので「OK」を押した

 これで、本来は完了のはずなのですけど、なぜか動作しなかったのですよねぇ。で、さらにやったことが以下の2つのコマンド実行後、再起動です。

  • sudo apt-get update
  • sudo apt-get dist-upgrade

 2つ目のアップデート、めっちゃ時間がかかりましたが、なんとか終わって再起動したら、マウスが使えるようになりました。ちょっと感動しました!