ベスパリブ

プログラミングを主とした日記・備忘録です。ベスパ持ってないです。

state machineの話(あるいはPython3での実装)

Statechart
このような記事を見つけました。
ステートマシン(state machine)実装のための本があることを初めて知りました。

ここで提案されている手法は、[* 状態変数を使うかわりに現在の状態を示す関数を使う]というものである。たとえば「あ」という状態は`あ()`という関数で表現し、「い」という状態は`い()`という関数で表現する。「あ」や「い」という状態の上位階層として「あ行」という状態が存在するので、それは`あ行()`のような関数で表現する。このように、[* あらゆる状態を関数として表現する]ところがミソである。

iPhoneフリック入力はこんな感じらしいです。はぇ~。

そういえば以前にPython3でstate machineを実装したことがあるので、そのときのやり方をメモって置きます。結構悩んで実装した記憶が…。

from enum import Enum

class StateMachine():
    class State(Enum):
        """ state machineの状態 """
        Start = 0 # 状態0(初期状態)
        S1    = 1 # 状態1
        S2    = 2 # 状態2
        S3    = 3 # 状態3
        S4    = 5 # 状態4

    def __init__(self):
        self.state = self.State.Start

    def update_state(self):
        """ state machineを更新する(状態遷移する) """

        if self.state is self.State.Start:
            """ 初期状態 """
            self.state = self.State.S1
            print("State: Start")
            return

        elif self.state is self.State.S1:
            """ 状態1 """
            self.state = self.State.S2
            print("State: S1")
            return

        elif self.state is self.State.S2:
            """ 状態2 """
            self.state = self.State.S3
            print("State: S2")
            return

        elif self.state is self.State.S3:
            """ 状態3 """
            self.state = self.State.S4
            print("State: S3")
            return

        elif self.state is self.State.S4:
            """ 状態4 """
            self.state = self.State.S1
            print("State: S4")
            return

実際に使うときは、ループを回して使います。

import time

if __name__ == "__main__":
    sm = StateMachine()
    while True:
        sm.update_state()
        time.sleep(1.0)

上記の実装の良いところを挙げると、
・state machineの実装がクラス内で完全に閉じている
・使うときはupdate_state()を呼び出すだけ
・引数を増やしやすい
・シンプル
くらいです。まあ特別なことはしていません。

実際に処理を実装するとわかるのですが、処理が長くなるとif文が長くなり可読性落ちるので、実際の処理はメソッド化するのが無難です。

if文の数が多くなると可読性が落ちたり、無駄な評価がされて遅いというデメリットが考えられるので、if文をディクショナリに書き換えればもっと良くなるかもしれません。試してないですけど。

あと、これはstate machineを実装する上で一般的に言える注意点だと思うのですが、状態を変更した後は(self.stateを変更した後は)すぐにreturnしなければいけません。さもなければ状態を追いかけることが難しくなります。以前、状態が変更された後も処理が続き、条件によってはさらに状態が上書きされ、そしてまた条件によっては上書きされ…というのが続き、最後にreturnされるstate machineを見たことがあるのですが(そしてそのプログラムのバグを追いかけていたのですが)、恐ろしくしんどいです。リーダブルコード7.5章にも書いてありますが、できることなら関数から早く返しましょう。

関数で複数のreturn文を使ってはいけないと思っている人がいる。アホくさ。

ちなみにPythonには既にstate machineを扱うライブラリがあるようです。独自実装で特殊なことする必要がなければこれを使うという手もあります。便利世の中~。
transitionsでステートマシンを扱う [Python]

ゲン、Gitに切れる

addコマンドの名前がむかつくんじゃ

おどりゃクソ森。「ステージする」んだからstageって名前のコマンドにせんか。

resetコマンドの名前がむかつくんじゃ

おどりゃクソ森。 「アンステージする」んだからunstageって名前のコマンドにせんか。
このresetコマンドが「ステージの取り消し」と「コミットの取り消し」を担当してるから初学者(わし)が混乱するんじゃ。--hard --softってなんじゃ。unstageコマンドとresetコマンドにわけんか。

checkoutコマンドで2つの異なった操作ができるのがむかつくんじゃ

・ブランチの切り替え
・ファイルを特定コミットの状態に戻す
この2つの操作が同じcheckoutでできるのが納得いかんのじゃ。
ブランチの切り替えはbranchコマンドでできると思ったんじゃがのう。ちがうんか。
というかgit reset --hard [commit]とgit checkout [commit]は何が違うんじゃ なにっ インデックスに追加したファイルが元に戻るかどうか? バカタレーッ それこそオプションで挙動を変えるべきで、コマンドを変えることないじゃないか 落ち着けあんちゃん 確かに混同しやすいがそもそもcheckoutとresetは別々の意味のコマンドで、たまたま似たような操作ができるだけじゃ やかましいっ! ヒッ おどりゃトサカにきたぞ隆太 わしはもうGitは使わん なにっそれは本当か 正気じゃないんじゃないか アホウわしゃ正気じゃ そもそもGitなんてものはアメリカがわしら日本人から和の心を奪うために戦後に導入した制度なんじゃ バージョン管理は昔ながらのコピーアンドペーストで良かったんじゃ わしはフォルダに日付を付けて管理する方法に戻すぞ よさんかゲン やめてーっ さよならGitまた来てコピペ ジャンジャンジャガイモサツマイモ…

Gitの勉強の仕方

Gitを初めて触ったのが2014年の1月なので、それから4年が経過しました。毎日Gitに触っているわけではないのでGit歴を単純計算できませんが、大体そのくらい触っているというわけです。

その間に、Gitは何回も挫折しました。重要なファイルを何回も消しました。エラーが修正できなくてリポジトリを何回も作り直しました。最近になってようやくGitを人並み程度に扱えるようになりました。

どうしてGitを習得するのにここまで時間がかかったのかと回顧してみると、おそらく勉強の仕方がまずかったのだなと思います。習うより慣れろの精神で使ってたらわかる様になると思っていましたが、そういうわけでもありませんでした。というかわかってないのに使うので、結果ファイルを消したりしてしまうわけです。そしてやる気が無くなるわけです。Gitはクソとか言い始めるわけです。

これはGitが苦手な人のための記事です。私は今でもGitが苦手なので偉そうなことは書けませんが、ここに書いてあることを意識して勉強しています、という自戒を込めての文章です。

Gitは難しいことを認める

Gitは難しい。
Gitは便利ではありますが、簡単ではない。

Gitチートシートの図を覚える

「習うより慣れろ」は大事ですが、最低限の知識は必要です。
最低限の知識とは何かを具体的に言うと、Gitチートシートに載っているものすべてです。
GitチートシートというのはGit Commands and Best Practices Cheat SheetAtlassian Git コマンド チートシート の日本語版をダウンロードしようなどです。私はGit ポケットリファレンスチートシートを使っています(改訂新版より以前のものはチートシート載ってない?)。

「共有リポジトリ」「ローカルリポジトリ」「インデックス」「作業ツリー」は必須の知識です。これらの用語と意味は覚えます。図のフローはしっかり覚えます。

代表的なコマンドが何をするものなのかも覚えます。addコマンドなら「作業ツリーの変更をインデックスに保存する」といったような理解です。当然ですが、自分が打ったコマンドが何をしているのか頭の中でイメージできるようになる必要があります。イメージしながらコマンドを打つ必要があります。慣れないうちはチートシートを見ながら、「今自分はここの操作をしてるんだ」とイメージしながらコマンドを打ちます。さもなければ、ファイルは雲散霧消するものと心得ます。

ブランチを切ったらどういう流れになるのか。mergeやrebaseしたらどういうコミット状態になるのか。これも最低限把握します。細かいfast-forwardやNon-fast-forwardなどは後で良いと思います。
チートシートに掲載されているコマンドは全て理解しているようになりましょう。

暗記するのは大変です。なので実際に手を動かして覚えるのが重要です。適当なリポジトリを作成し、消えても困らないファイルに対して思う存分addしてcommitしてbranch切ってfetchしてmergeまたはrebaseしてpushしましょう。イメージしたとおりのファイルの状態になっているか、コミットの流れになっているかを確認しながらやります。

本を買う

適当なサイトを参考にするのも良いですが、個人的にはGitポケットリファレンスをおすすめします。序文で「私はGitが嫌いでした」で始まる本書は私に合っていた気がします。技術評論社の回し者みたいですが、ポケットリファレンスの良いところを以下に示します。

ポケットリファレンスの良いところはコマンドの説明が充実しているので、「このコマンドって何するコマンドなの」「どんな書き方すればいいの」「こういうとき何のコマンド使えばいいの」というときに辞書的に参照しやすく、非常に便利です。

もうひとつ良いところは、情報ソースが統一されているということです。つまり、わからないときにポケットリファレンスを参照するようにすれば、以前参照した情報と、今回参照した情報が一致するので、記憶に残りやすいという点です。適当にググってもコマンドの使い方は出てきますが、人によってコマンドの書き方は様々だったり、以前参照したサイトがヒットしなかったりします。「以前となんかやり方違うような?まいっか」だとあまり記憶に残らないので、そういう「情報ソースを統一する」という意味で適当な本を買うというのは有用でした。

自分でまとめる

ポケットリファレンスがあれば良いかというとそうでもなくて、例えばド忘れしたときに毎回ポケットリファレンスを参照するのは面倒くさく感じます。まれによく使うコマンドなどは、自分なりにQiitaなりブログなりGmailの下書きなりにまとめておくと良いです。「こういうとき何のコマンド使うんだっけ」というとき、都度ググるよりかは、自分のやり方をメモしたものを参照したほうが、頭に残りやすくて良いです。


以上です。

Raspbian, Apache2でダイジェスト認証

Raspbian 8.0
Apache 2.4.10(Raspbian)

ダイジェスト認証に必要なモジュールを有効化する

$ sudo a2enmod auth_digest
$ sudo a2enmod authn_file
$ sudo a2enmod authz_user

Apache2の設定をする

$ sudo vi /etc/apache2/apache2.conf

(以下を適当なところに追加)

# Digest Authentication

<Directory /myproject/www/>

        AuthType Digest

        AuthName "DigestZone"

        AuthUserFile /myproject/www/.htdigest

        Require valid-user

</Directory>

ダイジェスト認証のファイルを作成する

$ sudo htdigest -c /myproject/www/.htdigest "DigestZone" root

Adding password for root in realm DigestZone.

New password:root

Re-type new password:root

Apache2の再起動

$ sudo service apache2 restart
$ sudo systemctl daemon-reload

Raspberry Pi のSDカードを縮小する方法

32GBから16GBのSDカードに縮小したかった。
たくさん記事はあり色々試しましたが、結局以下の方法でうまくいきました。

raspberry piのイメージファイルを小さな容量のメモリーカードにコピーする方法

EaseUS ToDo Backup Freeを使う方法も試しましたが、不良セクタの書き込み失敗エラーが解消できませんでした。

・2019/02/23追記
ラズパイでgpartedを実行、"e2fsck cannot continue aborting is mounted"エラー - ベスパリブ

Gitコマンド備忘録

この記事は都度更新します。
[最終更新]2019/12/09

Gitの基本知識

fetch, pull, pushの挙動

【Git】リモートからの取得とリモートへの反映で行っていること(fetch,pull,push) - Qiita

fetch, mergeの挙動とかを忘れたらコーヒー飲みながら上の記事を読む。

blame

ファイルの各行がいつ変更されたかを知ることができる

git blame [ファイル名]

・例

$ git blame hoge.py
c885c163 (committerName 2017-11-16 11:14:59 +0900  1) # -*- coding: utf-8 -*-
c885c163 (committerName  2017-11-16 11:14:59 +0900  2) def task1():
c885c163 (committerName 2017-11-16 11:14:59 +0900  3)     print("task1")
d9340aa0 (committerName 2017-11-17 12:33:44 +0900  4) def task2():
d9340aa0 (committerName 2017-11-17 12:33:44 +0900  5)     print("task2")

各行のコミット番号、コミッタ、日付時刻、行番号が確認できる。

コミット番号がどういうコミット内容だったかを知るには、showコマンドを使う。

branch

ブランチを確認する
*が付いているものが現在のブランチ

ブランチの確認

git branch
* master
  branchA
  branchB

clone

git clone [リポジトリのURL]

で、リポジトリのフォルダをダウンロードできる。
cloneとダウンロードって何が違うの?というと、cloneはそのプロジェクトのすべてのファイルのすべての歴史が含まれている。つまりコミット履歴などが含まれている。リポジトリからダウンロードするとそれらの歴史が含まれていないので、大抵はcloneする。

conflicts

競合ファイル一覧を表示する。本来の git コマンドには存在しない。

以下のコマンドを実行し、設定ファイルにconflictsコマンドのエイリアスを作成する。

$ git config --global alias.conflicts '!git ls-files -u | cut -f 2 | sort -u'

するとconflictsコマンドが使えるようになるので、実行したら競合ファイルの一覧が表示される。

$ git conflicts
src/mods/a.py
src/mods/b.py
web/main.py

checkout

checkoutでできることは大きく2つ。
・ブランチの切り替え
・ファイルを特定の状態に戻す
上2つがなんで同じコマンドなのかは不明。Gitのむかつきどころ。

ブランチの切り替え系

ブランチを切り替える
git checkout [ブランチ名]

ブランチ名の確認の仕方は、branchコマンドの項目を参照。

・例1

git checkout branchA

ブランチをbranchAに切り替える

・例2

git checkout -f branchA

ブランチを強制的にbranchAに切り替える
作業ツリーとインデックスの変更は破棄される

ブランチ作成と切り替えを同時に行う
git checkout -b branchC

あんまり使ったことない

ファイルを特定の状態に戻す系

ファイルを特定のcommitの状態に戻す

https://qiita.com/ritukiii/items/5bc8f74dbf4dc5d1384c

git checkout [コミット番号] [ファイルパス]

この操作は非常に使える。
例えばバグが発生したとき、どこのコミットからバグが発生したか調べるときに、ひとつひとつコミット番号を戻しながら使ったりする。

・コミット番号の調べ方
GitHubのページでファイルを開けばコミット番号が書いてある。
あるいはlogコマンドの項目を参照。

・例1

git checkout [コミット番号] hoge.txt

hoge.txtが[コミット番号]のときの状態に戻る

・例2

git checkout [コミット番号] .

全てのファイルが[コミット番号]のときの状態に戻る

・ 例3

git checkout HEAD -- hoge.txt

hoge.txtの現在の変更を破棄して、最新状態(HEAD)に戻す。

--の意味は「--以降の文字列をオプションとして扱わない」で、これにより-が混ざったファイル名を扱えるようになる。

・例4

git checkout HEAD^ -- hoge.txt

hoge.txtを1つ前のコミット(HEAD^)の状態に戻す。

log

コミット履歴を確認する

> git log
commit c4adc034f65a53d85b3fa8270e0e8239e4d45518
...

commit 8601edcfa95c72bf10c3b4479b59f1ffe488c5a0
...

ファイルのコミット履歴を確認する

$ git log --oneline hoge.txt
13aed9b (HEAD -> master, origin/master) updated
c4adc03 fix: 
8601edc fix: coding: utf-8

checkoutでファイルを特定のコミット状態に戻すときに便利

全体のコミット履歴を確認する

$ git log --oneline
13aed9b (HEAD -> master, origin/master) updated
c4adc03 fix:
ce5dd9d Update

reset

ステージを取り消す(addしたものを取り消す)

$ git reset HEAD [ファイル名]
$ git reset HEAD hoge.py

特定コミット状態に戻す

動作確認のためなど、過去のコミット状態を再現したいときは以下のようにする

git log  # コミットのハッシュ値を確認
git reset --hard 昔のコミットのハッシュ値

特定コミット状態から最新の状態に戻るには、

git reset --hard ORIG_HEAD

show

・ファイルの内容を見る
・コミットの差分を見る
上の2つの操作が同時にできる

そのコミット番号のときの変更を表示する

git show [コミット番号]

バグが発生したコミットを指定することで、ソースのどこが原因か追跡するのに便利。

$ git show c885c163
commit c885c1630f1a9f68ddf20c2583691eb999999999
Author: committerName <committer@mail.com>
Date:   Thu Nov 16 11:14:59 2017 +0900

    2017/11/16
    * Fix: センサ変更によるInput Errorを修正.

diff --git a/project/src/mymod/hoge/hoge.py b/project/src/mymod/hoge/hoge.py
index eeea885..71daae6 100755
--- a/src/updater.py
+++ b/src/updater.py
@@ -8,7 +8,6 @@ from mymod.sensor.am2320    import AM2320
-import time
 import datetime

最初にコミットメッセージが表示され、次にそのコミットによる変更のdiffがファイル毎に表示される。

ここでコミットメッセージに変更理由をきちんと書いていれば何を意図して変更したのかよくわかり、バグフィックスが容易になる。(コミットメッセージはちゃんと書こう)

特定のファイルのみshowしたいとき

$ git show [コミット番号]:[ファイル名]

・例

$ git show c8a5c163:src/mymod/sensor/am2320.py
import sys
sys.path.append("/project/src")
from mymod.const                import const
from mymod.helper               import helper
import datetime

class AM2320():
    """ センサAM2320のクラス """

そのコミット番号のときのファイルの内容が表示される。

stash

作業ツリー(とインデックス)の変更を、コミットせずに一時的に保存する。
スタッシュと呼ばれる領域に一時的に保存する。

作業ツリーとインデックスを一時的に保存する

$ git stash

作業ツリーを一時的に保存する(インデックスは保存しない)

$ git stash --no-keep-index

メッセージ付きで一時的に保存する

$ git stash save "message"

$ git stash save "fix: Change version"

スタッシュに保存したものの一覧

$ git stash list
stash@{0}: On master: fix: Change version
stash@{1}: WIP on master: 37c38ad fix: バグを修正

上に行くほど直近のスタッシュ。

スタッシュに保存した内容を作業ツリーに戻す

$ git stash apply <stash>
()
$ git stash apply stash@{0} 

スタッシュを指定しない場合、直近のスタッシュに戻る
技術評論社の ポケットリファレンス には「スタッシュを指定しない場合、直近のスタッシュが削除される」と書いてあるが、実際にはそのような動作はしなかった)

スタッシュに保存した内容を作業ツリーに戻した後、そのスタッシュを削除する

$ git stash pop <stash>
()
$ git stash pop stash@{0} 

使い方はapplyと同様。どちらか覚えれば良い。

スタッシュの削除

$ git stash drop <stash>
()
$ git stash drop stash@{0}

スタッシュを指定しない場合、直近のものが削除される

スタッシュの全削除

$ git stash clear

参考

  • Git ポケットリファレンス[改訂新版]