JUNのブログ

JUNのブログ

活動記録や技術メモ

Brainfuck インタプリタを作った

Brainfuckインタプリタを作りました.

Brainfuckって何?

Brainfuckって言語です.

言語仕様とかはWikipedia参照

ja.wikipedia.org

+++++++++[>++++++++>+++++++++++>+++>+<<<<-]>.>++.+++++++..+++.>+++++.<<+++++++++++++++.>.+++.------.--------.>+.>+.

これで Hello World! と出力するプログラムです.

qiita.com

インタプリタのコード

実装は愚直にWikipediaに書いてあるBrainfuckの言語仕様をそのまま実装していくだけです.

言語は一番慣れているPythonで書きました.

ファイルパスをコマンドライン引数で受け取って, それを処理する方がインタプリタっぽいですが, 面倒だったのでコードに直接書き込む感じにしました.

"""Brainfuckインタプリタ
言語仕様はWikipediaから: https://ja.wikipedia.org/wiki/Brainfuck
"""

# Trueにすると途中経過を可視化する
vis = True
output_str = ""

# Brainfuck program
code = "+++++++++[>++++++++>+++++++++++>+++>+<<<<-]>.>++.+++++++..+++.>+++++.<<+++++++++++++++.>.+++.------.--------.>+.>+."

# 少なくとも30000個の要素を持つバイトの配列(各要素はゼロで初期化される)
memory_arr = [0 for _ in range(30000)]

# インストラクションポインタ(プログラム中のある文字を指す)
inst_ptr = 0

# データポイント(前述の配列のどれかの要素を指す。最も左の要素を指すよう初期化される)
data_ptr = 0

while True:
    c = code[inst_ptr]

    if vis:
        import time
        print(code)
        vis_inst_ptr = "".join(["*" if i == inst_ptr else " " for i in range(len(code))])
        print(vis_inst_ptr)
        print(" ".join(map(lambda x: str(x).ljust(3), memory_arr[:10])))
        vis_data_ptr = "    ".join(["*" if i == data_ptr else "" for i in range(10)])
        print(vis_data_ptr)
        print(
            f"inst_ptr: {inst_ptr}\t"
            f"char: {c}\t"
            f"data_ptr: {data_ptr}\t"
            f"arr[data_ptr]: {memory_arr[data_ptr]}\n")
        time.sleep(0.01)

    if c == ">":
        data_ptr += 1
    elif c == "<":
        data_ptr -= 1
    elif c == "+":
        memory_arr[data_ptr] = memory_arr[data_ptr] + 1
    elif c == "-":
        memory_arr[data_ptr] = memory_arr[data_ptr] - 1
    elif c == ".":
        if vis:
            output_str += chr(memory_arr[data_ptr])
        else:
            print(chr(memory_arr[data_ptr]), end="")
    elif c == ",":
        memory_arr[data_ptr] = int(input("waiting for input: "))
    elif c == "[":
        if memory_arr[data_ptr] == 0:
            while code[inst_ptr] != "]":
                inst_ptr += 1
    elif c == "]":
        if memory_arr[data_ptr] != 0:
            while code[inst_ptr] != "[":
                inst_ptr -= 1

    inst_ptr += 1

    if inst_ptr == len(code):
        print("[interpreter output]: exit program!!")
        if vis:
            print(f"[program output]: {output_str}")
        break

可視化するとこんな感じ f:id:JUN_NETWORKS:20200301021956p:plain

感想

完走した感想(激うまギャク)ですが, めっちゃ簡単でお手頃なのに, 結構しっかりと達成感があって作ってて楽しかったです.

昔, 後輩が「Brainfuckインタプリタ作るのって簡単ですよ」的なことを言っていたのですが, 本当に簡単でした.

次はCコンパイラか自作OSか自作CPU作りたいなぁ...

「嫌われる勇気」を読んだ.

「嫌われる勇気」を読み終わったので, 感想とメモ.

嫌われる勇気

嫌われる勇気

感想

素晴らしかったが, 難しかった.

本書では "アドラー心理学" と呼ばれる 哲学(?)について 青年と哲人の対話形式で説明していく本だ.

基本的な流れは, 青年が疑問や困っていることを投げかけ, 哲人がその問題を解決するためにアドラー心理学における考え方や解決の仕方を具体例を上げて説明し, それに対して青年がつっかかり, 哲人が青年が突っかかってきた部分に対しての補足説明を行い, 青年は渋々納得し, 最後には全てが繋がり, 青年が理解, 納得し, アドラー心理学を受け入れる. という流れだ.

この青年が突っかかるというのが, どうも生意気な感じでどうにかこうにか哲人を論破してやろうという強気な姿勢で反論する訳だが, その反論が, 私が読んでいてモヤっとしたところを上手いこと言語化していてよかった. また, この青年の語彙の使い方大変おもしろく, 読み物としても純粋に楽しめた.

本書内では, アドラー心理学について他の哲学者の考えなどと比較することが多々あり, その際に哲学に関するバックグラウンドが無いと読むのは少し難しいように感じた. 私はKindleで読んだので, わからない単語があれば毎回その場で検索していたので読めたが, 紙の本だと毎回辞書引くか, Google先生に聞かないと行けない気がして, 紙の本で読むのは少し厳しいように感じた. それとも, 普通の人はこれくらいの哲学にに関する知識と語彙を備えているものなのだろうか.

アドラー心理学について言えば, 「理解は出来る. 共感も出来る. ただ実践できる気がしない」と言ったところだ.

以下ネタバレ注意

本書にも書かれているが, アドラー心理学には 常識へのアンチテーゼ という側面が含まれている. これはその通りに感じた. というのも, 読んでいて普段の思考では考えない考えなどが多く含まれていたからだ. 例えば, 「全ての悩みは対人関係が原因である」「褒めてはいけないし, 叱ってもいけない」「承認欲求を否定せよ」などなど.

しかし, それらについても, 「まぁ, そういう考え方も出来るな...」と理解しようと努力し, 多少理解できたような気もする.

しかし, 全てを理解し, 受け入れれた訳ではなく, 例えば 「他者を仲間だと思え」というのはわかるが, 「他者を信頼しろ(他者信頼). 裏切られて傷ついたらそれはそれで受け入れろ(自己受容)」というのがある. これいについては, 言いたいことはわかるし, それが出来たらきっと素敵なのだろうと思う. しかし, 現実はそうではなく, 他者を信頼し, 裏切られ, 傷つき, その傷ついた自分を受ける というのはいいのだが, 問題は裏切られた時に金銭を失ったり, 仕事を失ったりする可能性があるということだ. このような経済的な損失を被る可能性が有り, 馬鹿の一つ覚えみたいに他者全てを信頼するというのはどうなのかな... と思ったりもした.
しかし, 他者を信頼する ことで世界が良い方向に傾くというのも確かにそれはあるかもしれないとも思った.
昔見た "Yes Man" という映画を思い出した.

イエスマン “YES”は人生のパスワード [Blu-ray]

イエスマン “YES”は人生のパスワード [Blu-ray]

  • 出版社/メーカー: ワーナー・ホーム・ビデオ
  • 発売日: 2010/04/21
  • メディア: Blu-ray

この映画はYesYes教みたいなところに入って, 人の頼みを全てYesYesと答えまくったら最終的に人生がとても明るく幸せになった みたいな映画だったはず(適当)で, これはアドラー心理学における他者信頼をしまくった結果とも言えなくもないかな?という感じだ. 映画自体とても面白いので興味アレば是非見てほしい.

こうして書いてみるとまるで本書内に登場する青年のようになってしまった.

ここまで疑問点及び反論のようなものを書いたが, 逆にとても共感したことは, 「縦の関係ではなく, 横の関係を作る」, 「人生における最大の嘘, それは「いま, ここ」を生きないこと」, 「お前の顔を気にしているのはお前だけ」, 「劣等コンプレックス」などである.

私は自己承認欲求と劣等コンプレックスの塊なので, この辺についての章は特に素晴らしいと思った.

アドラー心理学は実践し, 身につけるのがとても難しいと思うが, 自分が良いと思った部分, 取り入れたいと思った部分などから少しずつ人生を良くしていけたらなと思う.

そして, 本書とは特に関係無いのだが, 今回は初めて電子書籍(Kindle)で本を買った. 今まで, 本は紙一択だと思っていたが, iPadを手に入れたので今回電子書籍で購入してみた. 結果的にはこれは大成功だった.
電子書籍, とにかく楽である. 読もうと思った五秒後にはスマホを開き本を読める状態に常にあるというのはなんとも快適なものだ. そして, スマホで本を読むのも, Kindleでこういう読み物なら, 文字サイズを変更したりすればかなり快適に読める. その結果, 後半は殆どスマホで読んでいた.

しかし, 本を読むという行為はどうも時間がかかるし, 出来ればながら作業で読めたらなぁと思っていたらAudibleというサービスがあったので次はこれを試してみようと思う.

また, 嫌われる勇気の続編が出ているらしいので, これをAudibleで購入してみいようと思う. Audibleは月額1500円なので, 1ヶ月に1冊読めば元が取れるのでかなりお得な気がする.

幸せになる勇気――自己啓発の源流「アドラー」の教えII

幸せになる勇気――自己啓発の源流「アドラー」の教えII

www.audible.com

追記) Audibleって1ヶ月に1冊だけなんですね...無制限だと思ってた...

LG 22MP48 のスタンドネックの外し方

日本語情報が無くて苦労したのでメモしとく。

 

https://m.youtube.com/watch?v=skGctm41DpA

 

この動画を見れば良い。

 

スタンドネックを外すときはスタンドの裏側にある穴みたいなところにマイナスドライバを入れて押し込む。そうすると爪が外れるので取れる。
f:id:JUN_NETWORKS:20200206033318j:image

TyporaとDropboxで快適メモ環境構築

快適なメモ環境を探して1年弱... いろいろ探して試しみましたが, 自分にピッタリ合うものはなかなか見つからず...

とりあえず現状いい感じのメモ環境を構築出来たので共有したいと思います.

私がメモ環境に求める条件は以下の通り

  • 使いやすい
  • オフラインで使える
  • クラウド上に自動でバックアップを取ってくれる
  • Markdownで書ける
  • カテゴリ(ディレクトリ)で分けることができる

とりあえず上記の全ての条件を満たした環境を構築したのでこの記事に書いていきたいと思います.

結論

Typora + Dropbox

です.

完成イメージ

f:id:JUN_NETWORKS:20200204014924p:plain

環境

MacOS でも Windows でもいけると思います.

やり方

Typoraのインストール

Typora公式サイト からインストールします.

Dropboxのインストール

Dropbox 公式サイト からDropboxアプリケーションをインストールします.

markdownファイルの保存先ディレクトリをDropboxと同期されているディレクトリ内に作成する.

Ubuntuの場合は, デフォルトでは /home/user/Dropbox/ でした. 他のOSは多分適当に探せば見つかります.

Typoraの設定

設定を開き以下の項目を設定します.

起動時にデフォルトで開くディレクトリを先程作成したディレクトリに変更します.

f:id:JUN_NETWORKS:20200204014749p:plain

次に自動保存を有効にします.

f:id:JUN_NETWORKS:20200204014826p:plain

自動保存の保存間隔は"高度な設定"を開き, autoSaveTimer の値を変更すれば変えることができる.

画像を保存するディレクトリを指定します.

これをすることで貼り付けた画像などが一箇所にまとまり, さらにはDropboxの自動同期の恩恵も受けられるので別のPCで開いた時に画像がなくなることがないです. また, 相対パスを使用するようにすることで他のPCで開いたときのパスの違いを受けにくくなるので, 画像をどの端末でも表示できます.

f:id:JUN_NETWORKS:20200204014844p:plain

最後に表示の欄からファイルツリー表示に変更すれば完了です.

f:id:JUN_NETWORKS:20200204014855j:plain

これで快適メモ環境が完成です!

感想

今回は今自分が使っているメモ環境と構築方法を紹介しました.

メモ環境論争はいつまで経っても正解が出ませんが, 自分はしばらくこれでいきます. 形式もMarkdownなので他のメモアプリを使うとなった場合でも移動しやすいと思います.

今回はTyporaに下書きを書いてからはてなブログにコピペするスタイルで記事を作成しましたが, 画像らへんや表示形式の違いなどで結局二度手間だった感があるので, ブログ記事の下書きにはあんまり向いてないのかなぁって気がしました.

2019年振り返りと2020年について

この記事を書いている時点で2020年になってから11日が経ってしまっていますが, そんなことは気にせず去年の振り返りと今年2020年の目標設定みたいなのをやっていきたいと思います.

前回こんな感じの1年振り返りの記事を書いたのは2月の誕生日だったので, 今年も誕生日に書くか〜って思っていましたが, やっぱり新年始まってすぐ書いた方がきりが良い気がするので今年以降は1月に書いていこうと思います.

ちなみに去年のやつはこれです.

jun-networks.hatenablog.com

そういえば去年の3月にTwitterのアカウント消えたので最初の四半期は何してるか詳しくわからないです.

1月

  • Kaggle の Quoraコンペに参加した.

そういえば1年前はKaggleやってましたね... 確か銅メダルだった気がします.

ただ, 多くの参加者が最終サブミットをしなかったから銅メダル取れただけだし, なんなら殆どコピペだったので記事にはしてないです.

f:id:JUN_NETWORKS:20200111011456p:plain

2月

2月は何したかなぁってカレンダー見たら結構色々ありましたね.

この中で特筆すべきことは 学内ハッカソン と 株式会社Sportip (以下 Sportip と呼ぶ) にジョインしたことでしょうか.

学内ハッカソンの方では確か Globally and Locally Consistent Image Completion という論文を実装しようとして, 途中で学習に2ヶ月かかることに気づいて制作を途中で辞めた気がします. 結局GLCICは完成しませんでしたが, これをしたことにより, 論文から実際にコードとして実装する難しさ, 論文の読み方 などを知れたので, 目に見える結果は出なかったけどいい経験だったと思います.
結局, 学内ハッカソンに出したのは前日に作った小さなブラウザミニゲームですが, そこでGLCICの話をする機会があり, そこで 株式会社EBILAB賞 をいただき, その後 社員の方々と会食をご一緒させて頂きました.

Sportipについては, この前からお誘いを頂いており, 何回か面談をした後にジョインしました.
現在もSportipの方で業務に参加させてもらってます. 業務内容に関しては言えませんが, スタートアップの速度と, Sportipがどんどん成長していくのを感じています. 頑張らねば!

3月

  • Mix Leep Study #36 に参加した
  • Mix Leep Study #37 に参加した.
  • めっちゃ業務やってた.
  • Twitterアカウント消えた.

こうして見るとめっちゃ Mix Leep 参加してますね.

特に#37 の量子コンピュータの回が印象に残っています.

Twitterアカウントに関しては以下の記事に全て書いたのでこっち読んで.

jun-networks.hatenablog.com

4月

  • 3年生になった.
  • 基本情報落ちた.

1年生の時にやらかしたので4年目でやっと3年生になれました. 長かった...!

基本情報は 「まぁいけるやろ」って感じで舐めプしたら落ちました. アホですね.

5月

  • Maker Faire Kyoto に参加した(一般)
  • UdemyのAWS講座をやった.
  • Computer Science (CS) に興味を持ち始める.

Maker Faire Kyoto は凄い作品多かったです(語彙力のNASA).

UdemyのAWS講座はめちゃくちゃわかりやすくて超おすすめしたいところですが, 今はもう販売していないのです. 残念.

6月

  • N高の説明会行った
  • LinkedInのリクルーターから連絡きた,
  • 競プロ少し始めた.

N高については転校出来そうならしようと思ってたけど, 単位的に卒業までに2年必要らしいのでやめた.

LinkedInのリクルーターから連絡来たけど, アルバイトじゃなくて正社員にしたくて連絡したらしいので話は無くなった.

このへんから競プロ始めた気がする. けど, ここ数ヶ月全くしていないのでそろそろやらねば

7月

  • 学校辞めたい欲が高まってた.
  • Netflix契約した(沼)
  • 中国語勉強し直そうとして1週間で辞めた.
  • 京アニの事件で病んでた.

僕はだいたい3ヶ月に一回くらい学校辞める辞めるって言ってるんですけど, この月はかなり辞めたい欲が強かったです. 理由としては3年生になって授業数が多り, 専門科目も多くなり, 好きな情報工学系の勉強ができなくなっていたからです. 自分の学科は電子機械系なので情報系は基本的に独学です. 一応デジタル回路の授業があるので完全に独学ではないですが...

中国語は, 昔中国に4年住んでいたので, その当時はネイティブスピーカーだったのですが, 今では全く話せません. 10年も話していないと仕方ないね.
でも, 昨今の中国語需要に伴い自分も勉強しなおそうとしましたが, 生きるのに必須では無いので1週間で辞めちゃいました. 英語は普段からコミュニケーションや情報収集で使うのでモチベーションが保つのですが, 中国語は普段使わないのでモチベーションが続きませんでした.

8月

アメリカ行った!! (帰りに台湾も行った)

8月はとにかく「アメリカに行った」これに尽きます. アメリカに行った件は全て以下の記事に書いたので気になる人は読んでください.

jun-networks.hatenablog.com

9月

普段私はリモートで業務に参加しているのですが, 夏休みの後半2週間は東京で対面で働いてました. 世間ではリモートワークを賞賛する声が多いようですが, やはり対面で仕事をしたほうが仕事はしやすい気がします.

10月

低レイヤーに手を出す

  • x86本 を読んだ
  • TOEICの勉強始めた
  • 弊校のロボコンの応援に行った
  • 授業が辛くて病んでた.

この辺から低レイヤーに興味を持ち始めました. 特にシステムプログラミングなどに興味が湧いてきました. 最初に読んだ本は x86CPUエミュレータ を作る本なのですが, これが本当にわかりやすくてめちゃくちゃ楽しかったのを覚えています. この本をシステムプログラミングの最初の本として読んだのは幸運でした. システムプログラミングは今までブラックボックだったコンピュータという高度な機械をどんどん理解出来るようになっていく過程がとんでもなく楽しいです.

jun-networks.hatenablog.com

ロボコン高専に3年半在学して初めて見に行きました. 自分はソフト系の人間なわけですが, それでもロボットが実際に動き, 競技を行っている姿には興奮しました. 私のクラスにもロボコン部の人間は数人いるわけですが, 本当に尊敬します.

授業に関しては, どうしても機械系科目に対して興味がでなくて辛かったです.
頭の中でCPUとかのことを考えながら過ごしてました. 微積の授業とデジタル回路の授業のおかげでメンタルは保たれました.

11月

  • Rust始めた
  • HHKBを手に入れた. 我流タイピングの矯正を始めた.
  • 42TokyoのWebテストに合格してPicineに参加確定した.

Rustを始めたのはいいけど, チュートリアル終わってから触って無いので何も覚えてないです. てかRustってシステムプログラミングとかに使うのはわかるねんけど, もう少し敷居の低い手軽に作れるやつないん? Web鯖とかか?

42Tokyoは, プレスリリースを見た瞬間に会員登録して, その1週間後にはWebテストやりました. ちなみにPicineは3月参加予定です. 受かるといいなぁ.

12月

  • CS を本気で勉強する決意をする
  • 全裸監督 見た
  • Turing Complete FM (TCFM) を発見した
  • TOEIC 初受験 670点 を取った
  • CPU制作のために論理ゲートなどをトランジスタで作った.

12月は1,2週間目はTOEICの勉強をしていた. おかげで初受験で670点というなかなかの成績が取れた. 個人的には800点以上は欲しいのでまた余裕が出来た時に受験してみます.

CSを本気で勉強する決意は, 前から勉強しなきゃって思ってたんですが, 以下のブログが決定的でした. 情報系の人は全員読んでほしい. 心に刺さりすぎて泣きたくなってくるので.

masa-lab.hateblo.jp

論理ゲートとか作ったりしたのは論理ゲートの仕組みを理解したかったためです. 一応DFFとかも作りたいなと思っています. CPU作るときにはIC使いますが, 論理ゲートなどがブラックボックなのもあれなのでとりあえず手を動かしてみた感じです.

TCFM は正直システムプログラミング初心者の僕には何言ってるか8割くらいわからないけれど, それでも聞いているとシステムプログラミング楽しそ〜〜って感じるのでたまに聞いてます.

2019総振り返り

気づいてたかもしれませんが, 今年は殆どハッカソン等のコンテストに参加していまんせん. なので, 対外的な成果や活動実績が殆ど無く「今年はあんまり何もしてないなぁ」って思ってましたが, こうして振り返ってみると対外的な成果は無くとも人間的にも技術者としても成長と経験を多く重ねることの出来た1年だったのではないかと思います.

学業の方に関しては, 専門科目が増えてきたことにより今まで気にならなかった学科と自分のやりたいこととのギャップに苦しんだ1年でした.

2019年で特筆すべきことはアメリカに行ったことでしょうか.
アメリカに行き多種多様な人々と会話したり,イベントに参加したりしたことにより, 自分の中の価値観が大きく変わりました. アメリカに行く前までは留年による同世代に対する遅れや高専に入って結構な年数が経ってしまい心理的に辞めるハードルが上がり辞めにくくなっていました. しかし, アメリカで 年齢,性別,人種などは関係なく, 大人でもやりたいことのために大学に入り直す人もいるし, アメリカでバスケをするためにアメリカの高校に通う同い年の日本人の子など多種多様な価値観の元で自由に生きて自分のやりたいことを追いかけている人達を沢山見ました. そこで自分も気づきました. 「自分の好きなように生きたらいいし, 世間の目なんか関係ない」 ということに. なのでこれからは僕の好きなように生きます. 学びたいことを学び, 行きたいところに行きます. たとえ中卒になろうが結婚できなかろうが悔いのない選択をしていきたいと思います.
アメリカに行ったことで自分の中の重荷が取れて楽になった気がします. アメリカに行かせてくれた私の両親に感謝の限りを捧げます.

2020年は...

2020年は人生の大きな転換期になる気がしています.

とりあえず今ある2020年の目標は

  • CPU, コンパイラ, OSを作る (つまりはシステムプログラミング!)
  • 自分のポートフォリオサイトを作成する.
  • 毎日充実した日々を送り, 好きなことをいっぱい勉強する.
  • いろんな分野をやってみて自分の進みたい道を探す
  • 定期的に運動する.
  • 42Tokyoに受かる!!

今年は低レイヤーなどを中心とした含めたCS系の基礎固めの年になりそうです.

プログラミングを初めて3年くらいが経とうとしていて, 最近になってやっとこCSといものを知りしっかりと勉強したいと思うようになりました. 今年は"勉強したい"ではなく"勉強する"年に出来たらいいなと思います. そして来年のこの時期に1年の振り返りをした時に今年よりも充実し満足し幸せな1年を送れたらいいなと思います.

なんだかポエムっぽい記事になっちゃいましたが, こんな感じでこの記事を締めさせていただきます. 今年もよろしくお願いします.

Python Logging SlackHandler の作り方

python で logging を使っていると, たまに 「このログをSlackに飛ばしたいなぁ」ってときがあります.

なのでログをSlackに投稿する Logging Handler 作りました.

Slack側の準備

Incoming Webhook アプリをSlackに導入します.

その他の管理項目 -> Appの管理 でワークスペースのアプリ設定ページにアクセス出来ます.

アプリの設定ページ内のAppディレクトリを検索して Incoming Webhook をSlackに追加します.

f:id:JUN_NETWORKS:20191130005317p:plain
Slack Incoming Webhook

Slackに追加 を押したらどのチャンネルに送信するかというのが出てくるので ログを送信したいチャンネルを選んでください. 選んだらIncoming Webhook インテグレーションの追加 を押してください.

f:id:JUN_NETWORKS:20191130005543p:plain

そうすると Incoming Webhook アプリの設定画面に飛ぶと思います.

その中にある Webhook URL をメモしておいてください.

試しに何かメッセージを送ってみましょう

import requests

webhook_url = "先程メモしたWebHookURL"
requests.post(webhook_url, json={"text": "Pythonから送信したテストメッセージ"})

これを実行すると先程指定したチャンネルにメッセージが来たと思います.

f:id:JUN_NETWORKS:20191130010223p:plain

テキストの投稿以外にも Incoming Webhook が出来ることは沢山ありますが, 今回は扱いません. 気になる人は Slackワークスペース内にある Incoming Webhook アプリ の設定ページを見てください.

SlackHandler を作る

いよいよ本題です. SlackHandler を作っていきます.

SlackHandler を作成する前に Python Loggingモジュールにおける Handler とは何かを理解しないといけません.

Pythonの公式ドキュメント によると

ハンドラは、(ロガーによって生成された) ログ記録を適切な送信先に送ります。

らしいです.

では作っていきましょう.

handlers.py というファイルを作って, 中身を以下の様にします.

import logging

import requests


class SlackHandler(logging.StreamHandler):

    def __init__(self, url):
        super(SlackHandler, self).__init__()
        self.url = url

    def emit(self, record):
        msg = self.format(record)
        self.send_message(msg)

    def send_message(self, text):
        message = {
            'text': text,
        }

        requests.post(self.url, json=message)

コードの解説をします.

SlackHandler が継承している logging.StreamHandler とは[Python公式ドキュメント] から引用すると

logging コアパッケージに含まれる StreamHandler クラスは、ログ出力を sys.stdout, sys.stderr あるいは何らかのファイル風 (file-like) オブジェクト (あるいは、より正確に言えば write() および flush() メソッドをサポートする何らかのオブジェクト) といったストリームに送信します。

つまりは, ログをどこかに送る 基礎的なHandlerです.

def __init__() では スーパークラスである StreamHandlerインスタンスメソッド __init__() を呼び出して StreamHandler の各種変数やメソッドの初期化をします.
また, 引数 url で webhook_url をインスタンス変数に登録しています.

def emit(self, record) は StreamHandler のメソッドで, 例によってPython公式ドキュメントから引用すると,

emit(record)

フォーマッタが指定されていれば、フォーマッタを使ってレコードを書式化します。 次に、レコードが終端記号とともにストリームに書き込まれます。 例外情報が存在する場合、 traceback.print_exception() を使って書式化され、 ストリームの末尾につけられます。

つまりは来たrecordをsetFormatterで設定したformat形式に変換してStreamに記録するというメソッドです. 今回の場合はSlackにログを送信したいのでこのメソッドをオーバーライドすればいいですね.
今回は来たrecordをformatの形式に変形するのはそのままに, その後Slackにメッセージを送信する関数 send_message() を作り, それを使ってformat形式に変形されたログを送信しています.

SlackHandlerを使う

SlackHandler を使う時は他の Logging Handler とほとんど同じように使えます.

以下のコードを実行するとSlackにログが送信されます.

import logging

from handlers import SlackHandler

webhook_url = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# logger の作成
logger = logging.getLogger(__file__)
logger.setLevel(logging.INFO)
# Slack Handler の作成
slack_handler = SlackHandler(webhook_url)
slack_handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s')
slack_handler.setFormatter(formatter)
logger.addHandler(slack_handler)


def main():
    logger.info("This is Logging Test!!")

    ans = 1 + 2
    logger.info(f"1 + 2 = {ans}")


if __name__ == "__main__":
    main()

f:id:JUN_NETWORKS:20191130031947p:plain
Slackに送られてきたメッセージ

SlackHandler (応用編)

SlackHandlerの改造

先程作成したSlackHandlerは最低限の機能を満たしていますが, 私が実際に使っているものはさらに改造を施したものです. 折角なのでご紹介します.

class SlackHandler(logging.StreamHandler):

    def __init__(self, url, username="", icon_emoji=":computer:"):
        super(SlackHandler, self).__init__()
        self.url = url
        self.username = username
        self.icon_emoji = icon_emoji

    def emit(self, record):
        msg = self.format(record)
        self.send_message(msg, record.levelno)

    def send_message(self, text, level_num):
        icon_emoji = self.icon_emoji
        if level_num > 30:  # Warning 以上
            # メンションを飛ばす
            text = "<@MEMBERID> " + text
            icon_emoji = ":exclamation:"

        message = {
            'text': text,
            'username': self.username,
            'icon_emoji': icon_emoji
        }

        requests.post(self.url, json=message)

emojiusername に関しては Slackのワークスペース内の Incoming Webhookアプリの説明に書いてあるのでそっちを見てほしいです. また, SlackのIncoming Webhooksを使い倒す - Qiita もわかりやすくていいと思います.

それではそれ以外の部分について解説します.

send_message(self, text, levelno) でlogging のレベルを渡しています. これにより, levelごとに処理を分けることが出来ます.
今回の場合は, WARNING以上のログはWebHookアプリのアイコンを❗に変更し, 私のアカウントに対してメンションを付けてログをSlackに送信しています. この部分をChannelIDに変えるとチャンネルに参加している全員に通知を飛ばせます.

f:id:JUN_NETWORKS:20191130164909p:plain
WARNING 以上のレベルは アイコンが変わる & ユーザーにメンションされる

メンションを飛ばす部分については SlackのIncoming Webhooksでメンションを飛ばす方法 - Qiita を見てください.

SlackHandler をもう少し高度に使う

先程の仕様例でもいいのですが, あれは少しスマートじゃないですし, 実際に使う際はHandlerやFormatを dictConfigfileConfig で管理すると思います. 自分の場合は dictConfig をいつも使っているので, 今回はそれで説明します.

私は dictConfig を使って. 以下のように設定しています.

import logging
import logging.config


# logging
LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'standard': {
            'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
        },
    },
    'handlers': {
        'default': {
            'level': 'INFO',
            'formatter': 'standard',
            'class': 'logging.StreamHandler',
            'stream': 'ext://sys.stdout',  # Default is stderr
        },
        # ファイル出力ハンドラー
        'file': {
            'level': 'INFO',
            'class': 'logging.FileHandler',
            'filename': "test.log",
            'formatter': 'standard',
        },
        'slack': {
            'level': 'INFO',
            'class': 'handlers.SlackHandler',
            'url': os.environ.get("SLACK_WEBHOOK_URL"),
            'username': 'Landing Classification Observer',
        }
    },
    'loggers': {
        '': {  # root logger
            'handlers': ['default', 'file', 'slack'],
            'level': 'INFO',
            'propagate': False
        },
    }
}


logging.config.dictConfig(LOGGING)
logger = logging.getLogger(__name__)

全ての設定を LOGGING で管理出来るようになりました.

ここでのポイントは

  • SlackのWebHook URLを環境変数で管理している.
    WebHook URLなどはバージョン管理などに入れたくない情報だと思うので, 環境変数に入れています. こちらのほうがセキュリティ的にもいいと思います.

  • SlackHandler のインスタンス作成時に必要な情報(引数) を全て LOGGING 内で完結している.
    コードの見た目的にも綺麗です.

おまけ

ここからは SlackHandler に必要な訳ではないが, あると便利かも? しれないものを紹介します.

SlackHandler用に新しいレベルを作る

標準出力ハンドラー と ファイルハンドラー ではログを出力したいけど, SlackHandlerには出力しなくない,,,
だからといって SlackHandler のレベルを WARNING に設定したくはない...

ってなった時には 新しいレベルを作りましょう!!

ちなみに python の logging の標準で用意されているレベルは以下のようになっています. Python 公式ドキュメントより以下の様になっています

Level Numeric value
CRITICAL 50
ERROR 40
WARNING 30
INFO 20
DEBUG 10
NOTSET 0

今回は INFO ってほど気軽な感じじゃないけど, WARNINGを使うほどでもない... けど Slackに送信してほしいので INFO と WARNING の間を取って レベル25 の IMPORTANT というレベルを作りましょう.

# add new level called IMPORTANT
IMPORTANT_LEVEL_NUM = 25
logging.addLevelName(IMPORTANT_LEVEL_NUM, "IMPORTANT")


def important(self, message, *args, **kws):
    if self.isEnabledFor(IMPORTANT_LEVEL_NUM):
        # Yes, logger takes its '*args' as 'args'.
        self._log(IMPORTANT_LEVEL_NUM, message, args, **kws)


logging.Logger.important = important

これで IMPORTANT という新しいレベルが logging に登録されました.

使う際には以下の様に Handler などを設定する前に定義してから使います.

import logging
import logging.config


# logging settings
# add new level called IMPORTANT
IMPORTANT_LEVEL_NUM = 25
logging.addLevelName(IMPORTANT_LEVEL_NUM, "IMPORTANT")


def important(self, message, *args, **kws):
    if self.isEnabledFor(IMPORTANT_LEVEL_NUM):
        # Yes, logger takes its '*args' as 'args'.
        self._log(IMPORTANT_LEVEL_NUM, message, args, **kws)


logging.Logger.important = important

# Define logging dict
# logging
LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'standard': {
            'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
        },
    },
    'handlers': {
        'default': {
            'level': 'INFO',
            'formatter': 'standard',
            'class': 'logging.StreamHandler',
            'stream': 'ext://sys.stdout',  # Default is stderr
        },
        # ファイル出力ハンドラー
        'file': {
            'level': 'INFO',
            'class': 'logging.FileHandler',
            'filename': "test.log",
            'formatter': 'standard',
        },
        'slack': {
            'level': 'IMPORTANT',
            'class': 'handlers.SlackHandler',
            'url': os.environ.get("SLACK_WEBHOOK_URL"),
            'username': 'Landing Classification Observer',
            'formatter': 'standard',
        }
    },
    'loggers': {
        '': {  # root logger
            'handlers': ['default', 'file', 'slack'],
            'level': 'INFO',
            'propagate': False
        },
    }
}


logging.config.dictConfig(LOGGING)
logger = logging.getLogger(__name__)


def main():
    logger.info("This is Logging Test!!")

    ans = 1 + 2
    logger.important(f"1 + 2 = {ans}")


if __name__ == "__main__":
    main()

これを実行すると, 標準出力とファイル出力ハンドラーに対しては 2つのログが出力されます.

f:id:JUN_NETWORKS:20191130155400p:plain
標準出力

出力された test.log の中身は以下のようになっています.

2019-11-30 15:53:35,869 [INFO] __main__: This is Logging Test!!
2019-11-30 15:53:35,870 [IMPORTANT] __main__: 1 + 2 = 3

そして, 肝心のSlackの方ですが, 以下のように IMPORTANT のログのみが送信されています.

f:id:JUN_NETWORKS:20191130155605p:plain
Slackに送られてきたメッセージ

いい感じですね!!

画像をSlackに自動投稿する

これは色んな所で既出なのであんまりガッツリは書きません. 詳しい解説は他のサイトを見てください.

コードだけ貼ります.

import requests


def upload_image(token, image, filename="", channels="logging", title="", initial_comment=''):
    """ 画像をSlackワークスペース内のチャンネルにアップロードする.
    別に画像以外でもいける

    Parameters
    ----------
    token : str
        Slack API Token.
        取得方法はggって
    image : binary
        画像のバイナリーデータ.
        ファイルから読み込む時は  open("filename", "rb")
        np.ndarray形式で変数imgに保持している時は  cv2.imencode('.jpg', img)[1].tostring()
    filename : str, optional
        slackに投稿される画像のファイル名, by default ""
    channels : str, optional
        投稿先のチャンネル名, by default "logging"
    title : str, optional
        Slack上でのタイトル, by default ""
    initial_comment : str, optional
        コメント(メッセージ), by default ''
    """
    files = {'file': image}
    params = {
        'token': token,
        'channels': channels,
        'filename': filename,
        'title': title,
        'initial_comment': initial_comment
    }
    url = "https://slack.com/api/files.upload"
    requests.post(url=url, params=params, files=files)

上記のコードでわからなかった場合は以下の記事読んでください.

qiita.com

あとがき

今回は Slack にログを送信できる SlackHandler を作成しました.

そもそもこれを作ろうと思ったのは機械学習の学習の学習の経過をSlackに送信したかったからです. そこで方法を調べたのですが, 多くの記事が別に関数を作って実装していました. 個人的には ログ関係は全て Logging にまとめてしまいたかったので今回SlackHandlerを作成しました. 調べた感じSlackHandlerについて書いてある記事は 日本語,英語 共に無かったのでもしかして初かな? 初だと少し嬉しいかもですね.

また, おまけの 新しいレベルを作る 部分は正直必須ではないです. 昔結構苦労して作ったのに現在使っていなくて悲しいので作り方を忘れる前に残しておきたかったので書きました. またどこかで使うかもしれないのでね.

もし, 参考になったり, 使って便利だったらこの記事のリンクを貼ったり, 共有してくれたら嬉しいです. 結構書くの時間かかったので.

てか今思ったんですけど, Slack APP に WebHook と uploadfile の権限持たせれば1つに統合出来るやん.

HHKB をUbuntuで使う

俺「HHKB気になるなぁ〜触ってみたいなぁ〜」

後輩「私のHHKB, 数日貸しましょうか?」

俺「😘😘😘😘😘😘😘😘😘😘😘😘😘」

ってことで前から触ってみたいなぁって思ってたHHKB(US)を後輩から貸してもらいました. やっぱり持つべきものは可愛い後輩ですね.

UbuntuでHHKB(US) を使うのに結構苦労したのでブログに残しておきたいと思います.

環境

  • HHKB Professional 2 US
  • Ubuntu 18.04 LTS

また, 以下のソフトウェアはインストール済みだと想定しています.

  • fcitx
  • mozc

PID スイッチの設定

HHKBにはPIDというキーボードの出力信号をハードウェア的に変えれるのがあるらしいですね,

f:id:JUN_NETWORKS:20191119073737p:plain

画像は 公式サイト より.

PIDスイッチは以下のようにしました.

PW1 PW2 PW3 PW4 PW5 PW6
OFF ON ON ON OFF ON

Macモードにしているのはこうしないと音量調整のショートカットキーが使えなくて不便だからです. UbuntuMacモードを使っていても特に違和感は感じないのでMacモードで大丈夫だと思います.

このPIDスイッチスイッチの構成は以下のブログのものを使いました. 細かい説明などはそちらでわかりやすく解説してらっしゃるので気になる方は見てください

the-tanaka.com

Ubutu キーボード設定

以下のコマンドでHHKB(US)を指定します.

$ dpkg-reconfigure keyboard-configuration

f:id:JUN_NETWORKS:20191119182757p:plain
HHKBを選ぶ

f:id:JUN_NETWORKS:20191119182821p:plain
配列はUS配列

f:id:JUN_NETWORKS:20191119182904p:plain
US配列にもなんか種類があるっぽい? まぁ普通のやつで大丈夫です.

f:id:JUN_NETWORKS:20191119182929p:plainf:id:JUN_NETWORKS:20191119182948p:plainf:id:JUN_NETWORKS:20191119183006p:plain
この辺は特に設定しなくてOK

/etc/default/keyboardの中身が先程設定したものになっているか

$ cat /etc/default/keyboard

こんな感じだったらOK

# KEYBOARD CONFIGURATION FILE

# Consult the keyboard(5) manual page.

XKBMODEL="hhk"
XKBLAYOUT="us"
XKBVARIANT=""
XKBOPTIONS=""

BACKSPACE="guess"

fcitxとmozc設定

/usr/share/ibus/component/mozc.xml の中の <layout>default</layout><layout>en</layout> に変える

$ cat /usr/share/ibus/component/mozc.xml
<component>
  <version>2.20.2673.102+dfsg-2ubuntu0.18.04.1</version>
  <name>com.google.IBus.Mozc</name>
  <license>New BSD</license>
  <exec>/usr/lib/ibus-mozc/ibus-engine-mozc --ibus</exec>
  <textdomain>ibus-mozc</textdomain>
  <author>Google Inc.</author>
  <homepage>https://github.com/google/mozc</homepage>
  <description>Mozc Component</description>
<engines>
<engine>
  <description>Mozc (Japanese Input Method)</description>
  <language>ja</language>
  <symbol>&#x3042;</symbol>
  <rank>80</rank>
  <icon_prop_key>InputMode</icon_prop_key>
  <icon>/usr/share/ibus-mozc/product_icon.png</icon>
  <setup>/usr/lib/mozc/mozc_tool --mode=config_dialog</setup>
  <layout>en</layout>
  <name>mozc-jp</name>
  <longname>Mozc</longname>
</engine>
</engines>
</component>

mozcは直接入力と日本語入力の2種類の状態を持っているので, fcitxの設定で入力メソッドをmozcのみにします.

f:id:JUN_NETWORKS:20191119185330p:plain

その下側にある歯車とキーボードアイコンでキーボードのレイアウトの設定が出来るのでUS配列に変更します

f:id:JUN_NETWORKS:20191119185454p:plain
キーボードレイアウトをUS配列にする

入力メソッドがmozc1つだけなので入力メソッド切り替えのショートカットキーは必要無いのでショートカットキーを無効にします

f:id:JUN_NETWORKS:20191119190442p:plain


そして, US配列では全角半角キーを使った入力切り替えが出来ないので別のキーを割り当てます.

別のキーの割り当て方は, mozcの設定の中のキーマップのところから変更します.

ベースにするキーマップは MS-IME を使います.

f:id:JUN_NETWORKS:20191119190948p:plain

keymap style の中で 半角全角キーを使っている動作を変更します.

f:id:JUN_NETWORKS:20191119191134p:plain

半角全角キーを使っている動作はIMEの 有効/無効 だけなのでこれに別のキーを割り当てます. Key部分を3回クリックすることでキーの変更画面が出てくるのでそこで変更出来ます.

自分は以下のように設定しました.

  • Ctrl + Space : IMEを有効にする
  • Chift + Space : IMEを無効にす

以上でUS配列で 日本語入力/英語入力 の切り替え, 入力が出来るようになったはずです