複数のカウンタの値をまとめて参照する

たとえばenumerateやsubsectionにおいては,その上位の階層のカウンタと,その時点でのカウンタを合わせたものを参照先として提供している.つまり,

\begin{enumerate}
    \item hoge
    \begin{enumerate}
        \item huga \label{huga}
        \item piyo
    \end{enumerate}
\end{enumerate}

\ref{huga} % 1a

というように,enumiienumiの値と合わせて参照できるようになっている.

さて,これを自前でやるにはどうするか,という話.

前提

\newcounter{hoge} % 一つ目カウンタ定義
\setcounter{hoge}{0} % カウンタリセット
\newcounter{huga} % 二つ目カウンタ定義
\setcounter{huga}{0} % カウンタリセット
\renewcommand{\thehuga}{\alph{huga}} % 二つ目のカウンタをアルファベットに
\newcommand{\hoge}{\refstepcounter{hoge}\setcounter{huga}{0}} % 呼び出すごとに一つ目のカウンタの値を進めると同時に,二つ目のカウンタをリセット
\newcommand{\huga}{\refstepcounter{huga}\thehoge\thehuga} % 二つ目のカウンタと一つ目のカウンタをまとめて呼び出す

\huga\label{huga} % 1a
\ref{huga} % a

このように,二つのカウンタの値を続けて出すだけの命令だと,参照時に後ろの一つしか参照することができない.

解決

\newcounter{hoge}
\setcounter{hoge}{0}
\newcounter{huga}
\setcounter{huga}{0} 
\renewcommand{\thehuga}{\hoge\alph{huga}} % 両方の値を出力するように\thehugaを再定義
\newcommand{\hoge}{\refstepcounter{hoge}\setcounter{huga}{0}}
\newcommand{\huga}{\refstepcounter{huga}\thehuga} % こっちは\thehugaだけ

\huga\label{huga} % 1a
\ref{huga} % 1a

出力命令を新規に作るときに二つの値を呼び出すのではなく,既存の出力命令\thehugaを加工して値を並べてやると,両方の値を並べて参照できる.これはjsarticle.clsなどのクラスファイルでenumerateのenumiiやsubsectionなどの定義において用いられている方法である.

つまり\labelと\refによる参照はカウンタそのものの値を参照しているというより,\theカウンタが呼び出している値を読んでいるということになる.この仕様はおそらくenumerateやsubsectionを実現するためのものだろうと思う.

gitリポジトリに入れた巨大なファイルの履歴を削除する

前回のエントリ時にはかなり余裕のあるクラウドにリモートを移動したので,ほかの関連ファイルも一括でgit管理してしまおうと思っていたのだが,割と面倒なことになった.関連ファイルには10GBを超える動画ファイルがいくつか含まれており,それをプッシュしたところ,40GBを超える巨大なpackファイルなるものが生成された.リモートリポジトリの容量も巨大化した.

問題は,巨大な動画ファイル自体をローカルから削除してコミットしてプッシュしても,packファイルはほとんど変化せず,容量もまったく減らなかったことである.考えてみれば,任意の時点のファイルを復元できるということはどこかにその情報が保持されているわけで,いかに圧縮しようと,可逆性を保っている限りある程度のサイズになるのはやむをえないはずである.

巨大な過去のコミットたちと,それらを保持していると思われるpackファイル.コミットにもファイルとしての実体があるようだ.これまで考えたこともなかった.

$ ls -laR /path/to/repo.git/| awk '{if ($5 > 100000000) print $0}'
 -r--r--r--    1 lingvisticae  staff  106954354 Oct 17 11:06 e8c69bbf05a87871c5819e80ad3ef1e6e20f37
 -r--r--r--    1 lingvisticae  staff  104546325 Oct 17 11:06 327871bad21d092f5096216943c5aec5ad4e6e
 -r--r--r--    1 lingvisticae  staff  693927392 Oct 17 11:07 27d5f7983a2d56b236b0828392d3e57aaac58f
 -r--r--r--    1 lingvisticae  staff  162961037 Oct 17 11:06 7e30e6b07a5d3163b3d00c40b711b8783085e4
 -rwxr-xr-x@   1 lingvisticae  staff   2039711683 Oct 13 10:25 pack-22a545fc7ddb1c86f7b43a24a8afe6bc1f99e8c7.pack
 -rwxr-xr-x@   1 lingvisticae  staff   2059296845 Oct 17 10:53 pack-3c10cef7a46302b019aeeaad360b77c3aaef9b0f.pack
 -rwxr-xr-x@   1 lingvisticae  staff   2042554513 Oct 16 12:32 pack-663d10973d4894882a90b44f9f36a9fae6a75083.pack
 -rwxr-xr-x@   1 lingvisticae  staff   2042555488 Oct 16 12:49 pack-a3d364ff5d91f08a7658dd9cc9d3f6f2b5070472.pack
 -r--r--r--    1 lingvisticae  staff  48236415702 Oct 17 13:55 pack-b77d47905579c9d82cabcaed2a81c200ce82f28e.pack

直接の問題としては単一のファイルが巨大化したことによりクラウドのアップロード上限に達した(OneDriveは15GBまで)ためにデバイス間共有ができなくなったことだが,ローカルとリモートでの合計がもとの動画のサイズの3倍超に膨れ上がったことによるディスク容量の圧迫も無視できない問題になった.

ローカルにも巨大なpackファイルがある.tmp_pack_7fYZnaというのはコミットかプッシュの過程で生成されるファイルで,packファイルと同等のサイズがあり,このファイルの生成途中でハードウェアのディスク容量の上限に達してしまい,操作が完了できなくなった.

$ ls -laR repo/| awk '{if ($5 > 100000000) print $0}'
 -r--r--r--  1 lingvisticae  staff  51481809799 Oct 17 17:25 pack-89968939d94286ec10a24a9a112f8e1eee02e842.pack
 -r--r--r--  1 lingvisticae  staff  19198967808 Oct 17 16:49 tmp_pack_7fYZna

調べてみると,Githubにも単一ファイルのアップロード上限があるらしく,大きいファイルの対策に迫られることは割とあることのようだった.とはいえ10GB超のファイルを差分管理しようとする愚か者は見当たらなかったが…

qiita.com
sutara79.hatenablog.com
confluence.atlassian.com

ということでこれらの記事を参考に,適当にファイルを逃がして容量を空け,巨大なファイルの履歴を消していく.

手順1

まず上記のAtlassianのサイトから,git_find_big.shというシェルスクリプトをダウンロードする.どのファイルが問題なのか明らかな場合には必ずしもやらなくていい.ダウンロードしたら,ローカルリポジトリの一番上に移動して実行する.

cd /path/to/repo
bash /path/to/git_find_big.sh

おそらくpackファイルが巨大であればあるほど走査に時間がかかるが,しばらくすると大きい方から上位10件が表示される.

$ bash /path/to/git_find_big.sh 
All sizes are in kB's. The pack column is the size of the object, compressed, inside the pack file.
size     pack     SHA                                       location
7996160  7985664  7e163546b1039166bc677445efb8a5018b46907b  DATA/151205/20151205132024_1.mpeg
7992388  7981966  f8029a9997d9ac6e36dafc09b748bde9819ee99e  DATA/151205/20151205132024_2.mpeg
7991920  7981168  7adbbf4a00c4490b1ceeb51919cd79af073e8a0c  DATA/151205/20151205132024_3.mpeg
6902880  6709183  cbb79aa49d554d61456f8bc92b8cd99a4ef54534  DATA/151205/20151205193243.mpeg
3908014  3908504  db8bb6f781757ca620949b5803845d4fe63a6f31  DATA/運転練習データ/GP010006.MP4
2707417  2694039  f4f1b570d8458e3dbec285ef1bdc2ffaaca49e69  DATA/task/T014_018_eaf/T014_018_MIX.mp4
2072616  2072176  4bb8a6c45e727b5c36852d463593ec43b065b510  DATA/task/T001_002_eaf/T001_002_MIX.mp4
828483   827771   7c6004efcb3546b4fcc4685c6626f35fa268d29c  DATA/割り込み?/割り込み.mov
727311   620069   03c97ee54cce7f0e3cf790936fdff8947a75439b  DATA/task/T014_018_eaf/T014_018_IC0A.wav
727311   239959   225fd98a5874a46a26270794f092ed75ca984e70  DATA/task/T014_018_eaf/T014_018_IC02.wav

どのファイルを歴史から抹消すべきか判明したので,消去のコマンドを実行する.ローカルレポジトリの一番上のままで,

$ git filter-branch --index-filter 'git rm --ignore-unmatch DATA/151205/20151205132024_1.mpeg' --tag-name-filter 'cat' -- --all

詳しいコマンドの解説はこちらにあるが,git filter-branch <command>で対象となるコミットにcommandを実行,そのコマンドがgit rm --ignore-unmatch <file>である.その他のオプションは,コミット名やタグを上書きするなど.

実行後しばらくはファイル数と時間が書き換わって進捗が表示されるが,終わり際には行が増えるようになる.ちなみにファイルが巨大で,かつ自動コミットのせいでコミットの数も膨大なので,一つのファイルの履歴を消すのに何時間もかかった.

$ git filter-branch --index-filter 'git rm --ignore-unmatch DATA/151205/20151205132024_1.mpeg' --tag-name-filter 'cat' -- --all
Rewrite 550f5fb201c4e0e9b3f896e3a9140247b528ecf5 (2062/2098) (6483 seconds passed, remaining 113 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite 4e50c358332cdb1a989ff88aa3361a175a47326c (2063/2098) (6485 seconds passed, remaining 110 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite 661bf83edaf4815b88dc1e815ebd7e4509aff8d5 (2064/2098) (6488 seconds passed, remaining 106 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite c55a58ecd3389ffc353c58815e681d6afdf5cfb7 (2065/2098) (6489 seconds passed, remaining 103 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite e54f6f2dfac36563828575c53875840582637746 (2066/2098) (6491 seconds passed, remaining 100 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite 02e20a9328284b658e43b31b905a8de6b271e19b (2067/2098) (6493 seconds passed, remaining 97 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite 52943f3967932981a7a3f3e296fb6ebd8567d5b8 (2068/2098) (6495 seconds passed, remaining 94 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite 48c1de8c0c30f3115aa80a1c82bdf3ad589280fb (2069/2098) (6496 seconds passed, remaining 91 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite fcdcf918dfa5dae27ffe3263664cd790f04cad2e (2070/2098) (6498 seconds passed, remaining 87 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite ce3759980539c0faf6769309ea2463b0e94320d0 (2071/2098) (6501 seconds passed, remaining 84 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite d3ccc268289bcb5a79e204126cf42ed14031ec25 (2072/2098) (6503 seconds passed, remaining 81 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite 6008b5c9ee5771156dbaac889e60536388ba34dd (2073/2098) (6505 seconds passed, remaining 78 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite 7f9dd3109bc228cba0b7bbf430500aa5e470d39a (2074/2098) (6506 seconds passed, remaining 75 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite 52ef1320e5f1bd71b97141ba6a4a6d2a3cf4716c (2075/2098) (6508 seconds passed, remaining 72 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite 6928570b778c18d3eec45ceeb7bc308c4bd2e9a0 (2076/2098) (6510 seconds passed, remaining 68 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite 2f8f04150059810abe68238851906e0bd0be093b (2077/2098) (6512 seconds passed, remaining 65 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite 62175087c45ad4020bf8593625cd0265a141243d (2078/2098) (6514 seconds passed, remaining 62 predicted)    rm 'DATA/151205/20151205132024_1.mpeg'
Rewrite dab1ba1509d113e652b34ba242318e9d5ecadc86 (2098/2098) (6545 seconds passed, remaining 0 predicted)    
Ref 'refs/heads/master' was rewritten
Ref 'refs/remotes/origin/master' was rewritten

ついで,当該ファイルのすべての参照を消し,無効なreflogを消し,ガベージコレクションする.

$ git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
$ git reflog expire --expire=now --all
$ git gc --prune=now

ちなみに1度目には,この操作を実行しながら,自動コミットを止めず,このレポジトリの中で別の作業をしていたため,レポジトリの歴史改変と新規コミットが同時に行われることになった.何がどう悪かったのかはわからないが,結果として以下のようなメッセージが出て,履歴消去は成功しなかった.自動コミットを止め,作業ディレクトリをレポジトリ外に退避し,また何時間もかかる操作を再実行する羽目になった.

WARNING: Ref 'refs/heads/master' is unchanged
WARNING: Ref 'refs/remotes/origin/master' is unchanged

成功するとgit_find_big.shには消したファイルが出なくなる.

最終的に操作が完了したら,git push --all --force git push --tag --forceでリモートに改変した歴史を反映する.うまくいかなければリモートを作り直す

手順2

原理的には上記の方法で,対象とするファイルをひとつひとつ消していけばいい.金曜の夜に適当にシェルスクリプトを回して帰宅すれば,きっと月曜の朝には終わっているに違いない.

for i in `bash git_find_big.sh| awk '{print $4}'`
do
    git filter-branch --index-filter "git rm --ignore-unmatch $i" --tag-name-filter 'cat' -- --all
    git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
    git reflog expire --expire=now --all
    git gc --prune=now
done

だが,上記のQiitaでも紹介されているように,このくらいの単純な条件での消去であればもっと素早く終える方法がある.

rtyley.github.io

ダウンロードしたbfg-1.13.0.jarを実行する.実行場所はどこでもよく,ローカルを引数に取る.オプション--strip-blobs-bigger-thanにメガバイト単位で削除対象ファイルのサイズ下限を指定する.

$ java -jar /path/to/bfg-1.13.0.jar --strip-blobs-bigger-than 400M /path/to/repo/

Using repo : /Users/lingvisticae/repo/../repo/.git

Scanning packfile for large blobs: 16924
Scanning packfile for large blobs completed in 532 ms.
Found 15 blob ids for large blobs - biggest=8184205312 smallest=457097216
Total size (unpacked)=35647620530
Found 3184 objects to protect
Found 3 commit-pointing refs : HEAD, refs/heads/master, refs/remotes/origin/master

Protected commits
 -----------------

These are your protected commits, and so their contents will NOT be altered:

 * commit a017320a (protected by 'HEAD')

Cleaning
 --------

Found 2098 commits
Cleaning commits:       100% (2098/2098)
Cleaning commits completed in 441 ms.

Updating 2 Refs
 ---------------

	Ref                          Before     After   
	------------------------------------------------
	refs/heads/master          | a017320a | d765a52f
	refs/remotes/origin/master | ee062ba0 | b1563980

Updating references:    100% (2/2)
...Ref update completed in 14 ms.

Commit Tree-Dirt History
 ------------------------

	Earliest                                              Latest
	|                                                          |
	..........................................................DD

	D = dirty commits (file tree fixed)
	m = modified commits (commit message or parents changed)
	. = clean commits (no changes to file tree)

	                        Before     After   
	-------------------------------------------
	First modified commit | 46ac3103 | a1c7efed
	Last dirty commit     | 8257f140 | 289b5708

Deleted files
 -------------

	Filename                Git id             
	-------------------------------------------
	20151205132024_2.mpeg | f8029a99 (7.6 GB)  
	20151205132024_3.mpeg | 7adbbf4a (7.6 GB)  
	20151205132024_4.mpeg | cfb88c78 (435.9 MB)
	20151205193243.mpeg   | cbb79aa4 (6.6 GB)  
	C001_002_MIX.mp4      | b127d5f7 (661.9 MB)
	GP010006.MP4          | db8bb6f7 (3.7 GB)  
	T001_002_IC01.wav     | ee84c96c (543.9 MB)
	T001_002_IC02.wav     | ad898d79 (543.9 MB)
	T001_002_IC0A.wav     | a6b8e1b5 (543.9 MB)
	T001_002_MIX.mp4      | 4bb8a6c4 (2.0 GB)  
	T014_018_IC01.wav     | 3a2e8a8b (710.3 MB)
	T014_018_IC02.wav     | 225fd98a (710.3 MB)
	T014_018_IC0A.wav     | 03c97ee5 (710.3 MB)
	T014_018_MIX.mp4      | f4f1b570 (2.6 GB)  
	割り込み.mov              | 7c6004ef (809.1 MB)


In total, 72 object ids were changed. Full details are logged here:

	/Users/lingvisticae/repo/../repo.bfg-report/2019-10-17/21-32-11

BFG run is complete! When ready, run: git reflog expire --expire=now --all && git gc --prune=now --aggressive


 --
You can rewrite history in Git - don't let Trump do it for real!
Trump's administration has lied consistently, to make people give up on ever
being told the truth. Don't give up: https://www.theguardian.com/us-news/trump-administration
 --

ものの1秒もかからず終了した.

終了したら同様に参照の処理をする.

$ git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
$ git reflog expire --expire=now --all
$ git gc --prune=now

f:id:lingvisticae:20191018110418p:plain
縮小に成功したリモート

f:id:lingvisticae:20191018110520p:plain
縮小に成功したローカル

gitのリモートリポジトリを変更した

以前のエントリで設定した自動プッシュプル運用のgitリモートリポジトリを別のクラウドに移動した.

livingdead0812.hatenablog.com

livingdead0812.hatenablog.com

理由は単に無料のOneDriveが容量不足になってきたから.

ちなみに移動先の候補としては,

  • Dropbox(無料版): 合計容量不足
  • box sync(無料版): 単一ファイルのサイズ制限が小さすぎる (<250MB)
  • iCloud Drive(50GBプラン): 未検討(微妙そう)
  • Google Drive: 未検討(こっちに移動するかも)

で,結局職場が提供してくれているOneDrive for Businessにした.理由は容量にかなり余裕がある(1TB中60GBくらいしか使っていない)ため.

なおまったく最善の選択ができていないが,この運用をするにあたって最大限重視すべきなのは,変更してから変更をアップロード開始するまでの時間の短さだと思う(この条件ではOneDriveは最善どころか最悪に近く,経験の限りではDropboxがもっとも優れている).現状,iCloudGoogle Driveがこの条件でみてどうなのかが十分検討できていない.Google Driveは悪くなさそうだが,iCloudは割と同期のトラブルが多いので,あまり向いていないような気がする.

ただ,ストレージに余裕があればトラックしたいファイルはいろいろあるし,トラックしなくてもまとめて置いておくと楽なものがいろいろあるので,結局はストレージの余裕を優先することになると,iCloudGoogle Driveも十分な容量というわけにはいかず,それなりに支払うならDropboxがいいような気がするので,これら二つに移行することはないかもしれない.なんにせよリモートの移動自体は以下に示すようにかなり簡単なので,気軽にいろいろ試してもいいように思う.

ちなみにiCloudの50GBプランは1ドル/月なので,これくらいなら特に躊躇はないのと,はるか昔にPDFへのアノテーションを共有するためにiPadにインストールしたアプリではiCloudが最も使いやすかったので,容量を拡張してかたっぱしからPDFを突っ込んでいる.このアプリは開発を終了してしまったようだし,もう何年も経つので状況も変わっているだろうから,この条件を優先する理由はもうないのかもしれない.

livingdead0812.hatenablog.com

移動の方法

やることは大きく分けて,新しいリモートを作ってそちらにプッシュするのと,他のデバイスで新しいリモートにプッシュプルする設定をするのがある.

新しいリモートを作って内容をコピーする

まずターミナルでローカルリポジトリに移動し,現在のリモートを確認.

cd path/to/local
git remote -v 
# origin    localhost:path/to/remote/remote.git (fetch)
# origin    localhost:path/to/remote/remote.git (push)

必要ならローカルの状態を最新にする

git pull origin master

ローカルからリモートの情報を削除(リモート自体の削除ではない)

git remote remove origin

新しいリモートを作る

cd path/to/new/remote
mkdir remote.git
cd remote.git
git --bare init --share

このときクラウドフォルダではなくサーバ上に作るのであれば,sshでログインして同様にする.
またGithubなどのgitサービスならこのプロセスは不要.

新しいリモート(まだ空)にローカルをプッシュ

cd path/to/local
git remote add origin localhost:path/to/new/remote
git push origin master

サーバやGithubならlocalhostではなくhttpsなどで場所を指定.

他のデバイスで新しいリモートに接続する

ローカルに移動し,リモートの情報を書き換えて,プル

cd path/to/local
git remote remove origin
git  remote add origin localhost:path/to/new/remote
git pull origin master

最後にログを見て,移行できていることを確認

git log

参考
qiita.com

オブジェクト指向 ≒ classを定義して使う,ということ?

オブジェクト指向ってなに?

【Python】オブジェクト指向プログラミングの概念と書き方 | HEADBOOST
オブジェクト指向がわからない! そんなあなたの脳味噌をオブジェクト脳にする準備体操:CodeZine(コードジン)
初心者向けに徹底解説!オブジェクト指向とは?
オブジェクト指向超入門

オブジェクト指向の定義は、専門家の間でも微妙に食い違っているが、 「ソフトウェアで扱う事柄について、データと操作(メソッド)をまとめて1つのオブジェクトとして捉える」 ということは共通している。オブジェクト指向ではオブジェクトを基本単位として考え、 1つのオブジェクトの中には、データとメソッドが備わっているとする。

この説明だけ読むと,データ(変数)と操作(関数)をまとめて1つのオブジェクト(クラス)にまとめれば,とりあえず最大公約数的な定義を満たすように思われる.

たとえば,テキストファイルを読み込み,指定した文字列を含む行のみを出力するプログラムをサンプルにする.
【Python】オブジェクト指向プログラミングの概念と書き方 | HEADBOOSTの説明にある,いわゆる手続き型の書き方だと,

import sys

lines_list = []

with codecs.open(sys.argv[1], 'r', 'utf-8') as fin:
    for line in fin:
        if sys.argv[2] in line:
            lines_list.append(line.rstrip('\r\n') 

for line in lines_list:
    print line

これを関数型というのにしてみると,こんなところだろうか.

import sys


def get_key():
    key = sys.argv[2]
    return key


def make_lines_list(key):
    with codecs.open(sys.argv[1], 'r', 'utf-8') as fin:
        lines_list = [line.rstrip('\r\n') for line in fin if key in line]
        return lines_list
        

def output_lines(lines_list):
    for line in lines_list:
        yield line


if __name__ == '__main__':
    for i in output_lines(make_lines_list(get_key)):
        print i

どこといわずおかしいような気もするが,少なくとも関数の引数と戻り値だけで処理のやりとりをしている.
関数型プログラミングはまず考え方から理解しよう - Qiitaによるとループは関数型らしくないらしい)


さらにこれをオブジェクト指向にするには,こうすればいいのか?

import sys

class FindLine:
    def __init__(self):
        filename = ''
        key = ''

    def make_lines_list(self):
        with codecs.open(self.filename, 'r', 'utf-8') as fin:
            lines_list = [line.rstrip('\r\n') for line in fin if self.key in line]
            return lines_list        

    def output_lines(self, lines_list):
        for line in lines_list:
            yield line

if __name__ == '__main__':
    find_line = FindLine()
    find_line.filename = sys.argv[1]
    find_line.key = sys.argv[2]
    lines_list = find_line.make_lines(find_line.key)

    for i in find_line.output_lines(lines_list):
        print i

まあ,クラスオブジェクトを定義し,インスタンスオブジェクトを生成して処理を行っている.
このコードでは意味はないが,オブジェクト指向の利点としてよく言及されるところの,
同様のインスタンスを生成したかったり,機能を引き継ぐ別のクラスを作りたかったりするなら,容易にできるだろう.

本当にただこれだけのことなのか?

オブジェクト指向プログラミング OOP(Object Oriented Programming)の基本的な仕組み – Device Configuration

「クラス」「ポリモーフィズム」「継承」をOOPの三大要素と呼ぶ。構造化言語では解決しなかった2つの課題である「グローバル変数」「貧弱な再利用」を解決している。
  • OOPにはグローバル変数を使わずに済ませる仕組みが備わっている。
  • OOPには共通サブルーチン以外の再利用を可能にする仕組みが備わっている。

もちろん言葉の上では「これだけのこと」ではないが,仕組み上,ポリモーフィズムはクラスの書き方の話で,継承だってクラスの話である.
したがって事実上,クラスを使い,かつそれ以外のもの(グローバル変数をはじめとする,クラスの外に定義するもの)をできるだけ排除することで実現されると考えて差し支えないように思う.
少なくともPythonの場合は.

xmlを編集・生成する方法についての備忘

構造を変えずに属性やテキストに編集を加える場合

読み込んだファイルオブジェクト(Elementオブジェクト)を直接編集し,別名のオブジェクトを作って書き出す.

from lxml import etree as ET

tree = ET.parse(filename)
root = tree.getroot()

# 編集するテキストをXPathで取り出し,編集
for t in root.findall('./path/to/node'):
    t.text = t.text.replace('hoge', 'huga')

# ElementTreeオブジェクトに変更を反映し,ファイルに書き出す
new_tree = ET.ElementTree(root)
new_tree.write(filename, encoding='utf-8', xml_declaration=True)

構造を変えずに値を編集するだけなら,変えたい値を代入するだけで変更できる.
ただしもちろんElementTreeオブジェクトを生成し直して書き出す必要がある.

読み込んだxmlの一部を使い,構造を大きく変える場合

from lxml import etree as ET

tree = ET.parse(filename)
root = tree.getroot()

# ルートの作成
# ET.Elementで指定したタグをルートにする(ここではもとのまま)
new_root = ET.Element(root.tag)
# 辞書形式でルートの属性を指定(ここではもとのまま)
for k, v in root.attrib.items():
    new_root.set(k, v)

# ルートより下のノードを作成
# 元のファイルのノードを一旦格納
n1 = root.find('./path')
# ET.SubElementで,直上のノードとノードのタグを指定
node1 = ET.SubElement(new_root, n1.tag)
# 属性の追加
for k, v in n1.attrib.items():
    node1.set(k, v)

# 元のファイルのノードの一部を編集して追加
n2 = root.find('./path2')
node2 = ET.SubElement(new_root, n2.tag)
for k, v in n2.attrib.items():
    node2.set(k, v)
# テキストの追加
node2.text = 'hoge'

# 元のファイルにないノードを追加
node3 = ET.SubElement(new_root, 'huga')
node3.set('pura', 'piyo')
node3.text = 'picopico'

new_tree = ET.ElementTree(new_root)
# pretty_print=Trueをしないと改行やインデントがされない
new_tree.write(filename, encoding='utf-8', xml_declaration=True, pretty_print=True)

構造を変える場合にはxmlのパーツになるElementオブジェクトを生成する必要がある.

lxmlとxml.etree.elementtree

どちらもほぼ同じメソッドをもっていて,解析するだけなら同様に使えるが,
生成する際のpretty_printオプションにはxml.etree.elementtreeは対応していない.

インストールオプションが使えなくなったffmpegで,字幕などのオプションを利用する方法

以前はbrew install ffmpeg --with-libassなどのインストールオプションを使うことで,最小ビルド以外のffmpegの機能が使えるようになったが,たぶん今年の初めくらいからインストールオプションをサポートしなくなったらしい.
https://discourse.brew.sh/t/ffmpeg-options-missing/3935discourse.brew.sh

どうもメジャーなオプションは最初から一緒にインストールされるようになったらしいものの,--with-libassあたりはメジャーとはみなされていないらしい.

いろいろ調べたところ,サードパーティのパッケージでそれらをまとめてくれているものがあった.
github.com

オプションの一覧は載っていないが,おそらく

brew install varenc/ffmpeg/ffmpeg --with-any-option

とし,以前使えたインストールオプションを指定すればいいように見える.

なお,面倒な向きは,

brew install varenc/ffmpeg/ffmpeg $(brew options varenc/ffmpeg/ffmpeg --compact)

とすると可能なすべてのオプションがインストールされる.

ただし,dependencyをすべてインストールしきるのに相当な時間がかかった(ネットワークが貧弱な場所でやっていたせいでもある).
特にrust. 動いているのか心配だった.

なお,さらに,dependencyをすべてインストールし終わり,いよいよffmpegと感動の再会としけ込む段階になって,

ERROR: DeckLinkAPI.h not found

という見たことのないエラーが出て,インストールが中断された.
homebrewでdecklinkというのを検索しても出てこず,検索するとなにやらサウンドカードのようなものの会社がヒットする.
www.blackmagicdesign.com

先のvarenc氏のGitHubにも,

The DeckLink SDK has to be installed before running the FFmpeg formula.

とある.
(英語のinstallにはハードウェアの設置という意味もあることに注意)

解決は,varenc氏のissuesに投稿してくれている人がいた.

If not installed, you can use Homebrew to install it:

brew install amiaopensource/amiaos/decklinksdk

ということでこれもサードパーティbrewパッケージを利用するらしい.

最終的にこれだけやって,無事ffmpegの(試した範囲での)一通りの機能が使えるようになった.

2020/01/28追記

現在varenc/ffmpeg/ffmpegはdependenciesのバグによりインストールできない状態にあるとみられる.同様の機能を提供するhomebrew-ffmpeg/ffmpeg/ffmpegを利用するとインストール・アップデートができる.

CompilationGuide/macOS – FFmpeg

Tkinterでpygameを使って音を出すときの注意点

たとえばこんな感じで,単に音を鳴らすだけのウィンドウを作ったとして,

import tkinter as tk
import pygame

pygame.init()
pygame.mixer.init()
se = pygame.mixer.Sound('./se_file.wav')

root = tk.Tk()
root.title('hoge')

def ring():
    se.play()

button = tk.Button(root, command=ring)
button.pack()

root.mainloop()

以下のようなエラーが出て起動しないことがある.

pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
2019-05-31 13:15:16.969 python[5856:443232] 13:15:16.968 WARNING:  140: This application, or a library it uses, is using the deprecated Carbon Component Manager for hosting Audio Units. Support for this will be removed in a future release. Also, this makes the host incompatible with version 3 audio units. Please transition to the API's in AudioComponent.h.
2019-05-31 13:15:17.033 python[5856:443232] -[PYGSDLApplication _setup:]: unrecognized selector sent to instance 0x7f8682f5ccd0
2019-05-31 13:15:17.034 python[5856:443232] An uncaught exception was raised
2019-05-31 13:15:17.034 python[5856:443232] -[PYGSDLApplication _setup:]: unrecognized selector sent to instance 0x7f8682f5ccd0
2019-05-31 13:15:17.034 python[5856:443232] (
	0   CoreFoundation                      0x00007fff93f1e452 __exceptionPreprocess + 178
	1   libobjc.A.dylib                     0x00007fff8b16ef7e objc_exception_throw + 48
	2   CoreFoundation                      0x00007fff93f8818d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
	3   CoreFoundation                      0x00007fff93e8e4c1 ___forwarding___ + 1009
	4   CoreFoundation                      0x00007fff93e8e048 _CF_forwarding_prep_0 + 120
	5   Tk                                  0x0000000102478948 TkpInit + 476
	6   Tk                                  0x00000001023f3a6e Tk_Init + 1799
	7   _tkinter.cpython-36m-darwin.so      0x00000001022d0da4 Tcl_AppInit + 84
	8   _tkinter.cpython-36m-darwin.so      0x00000001022d0a7b _tkinter_create + 1115
	9   libpython3.6m.dylib                 0x0000000101a32a81 _PyCFunction_FastCallDict + 497
	10  libpython3.6m.dylib                 0x0000000101ab4787 call_function + 439
	11  libpython3.6m.dylib                 0x0000000101ab0f62 _PyEval_EvalFrameDefault + 27346
	12  libpython3.6m.dylib                 0x0000000101ab51ef _PyEval_EvalCodeWithName + 2447
	13  libpython3.6m.dylib                 0x0000000101ab5d92 _PyFunction_FastCallDict + 738
	14  libpython3.6m.dylib                 0x00000001019ea677 _PyObject_FastCallDict + 247
	15  libpython3.6m.dylib                 0x00000001019ea795 _PyObject_Call_Prepend + 149
	16  libpython3.6m.dylib                 0x00000001019ea4b0 PyObject_Call + 96
	17  libpython3.6m.dylib                 0x0000000101a4aaed slot_tp_init + 125
	18  libpython3.6m.dylib                 0x0000000101a46e29 type_call + 313
	19  libpython3.6m.dylib                 0x00000001019ea645 _PyObject_FastCallDict + 197
	20  libpython3.6m.dylib                 0x0000000101ab4688 call_function + 184
	21  libpython3.6m.dylib                 0x0000000101ab0f62 _PyEval_EvalFrameDefault + 27346
	22  libpython3.6m.dylib                 0x0000000101ab51ef _PyEval_EvalCodeWithName + 2447
	23  libpython3.6m.dylib                 0x0000000101aaa3d4 PyEval_EvalCode + 100
	24  libpython3.6m.dylib                 0x0000000101ae00f1 PyRun_FileExFlags + 209
	25  libpython3.6m.dylib                 0x0000000101adf8a3 PyRun_SimpleFileExFlags + 851
	26  libpython3.6m.dylib                 0x0000000101af870f Py_Main + 3535
	27  python                              0x00000001019d5df8 main + 232
	28  libdyld.dylib                       0x00007fff8c0cb5ad start + 1
)
2019-05-31 13:15:17.162 python[5856:443232] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[PYGSDLApplication _setup:]: unrecognized selector sent to instance 0x7f8682f5ccd0'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff93f1e452 __exceptionPreprocess + 178
	1   libobjc.A.dylib                     0x00007fff8b16ef7e objc_exception_throw + 48
	2   CoreFoundation                      0x00007fff93f8818d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
	3   CoreFoundation                      0x00007fff93e8e4c1 ___forwarding___ + 1009
	4   CoreFoundation                      0x00007fff93e8e048 _CF_forwarding_prep_0 + 120
	5   Tk                                  0x0000000102478948 TkpInit + 476
	6   Tk                                  0x00000001023f3a6e Tk_Init + 1799
	7   _tkinter.cpython-36m-darwin.so      0x00000001022d0da4 Tcl_AppInit + 84
	8   _tkinter.cpython-36m-darwin.so      0x00000001022d0a7b _tkinter_create + 1115
	9   libpython3.6m.dylib                 0x0000000101a32a81 _PyCFunction_FastCallDict + 497
	10  libpython3.6m.dylib                 0x0000000101ab4787 call_function + 439
	11  libpython3.6m.dylib                 0x0000000101ab0f62 _PyEval_EvalFrameDefault + 27346
	12  libpython3.6m.dylib                 0x0000000101ab51ef _PyEval_EvalCodeWithName + 2447
	13  libpython3.6m.dylib                 0x0000000101ab5d92 _PyFunction_FastCallDict + 738
	14  libpython3.6m.dylib                 0x00000001019ea677 _PyObject_FastCallDict + 247
	15  libpython3.6m.dylib                 0x00000001019ea795 _PyObject_Call_Prepend + 149
	16  libpython3.6m.dylib                 0x00000001019ea4b0 PyObject_Call + 96
	17  libpython3.6m.dylib                 0x0000000101a4aaed slot_tp_init + 125
	18  libpython3.6m.dylib                 0x0000000101a46e29 type_call + 313
	19  libpython3.6m.dylib                 0x00000001019ea645 _PyObject_FastCallDict + 197
	20  libpython3.6m.dylib                 0x0000000101ab4688 call_function + 184
	21  libpython3.6m.dylib                 0x0000000101ab0f62 _PyEval_EvalFrameDefault + 27346
	22  libpython3.6m.dylib                 0x0000000101ab51ef _PyEval_EvalCodeWithName + 2447
	23  libpython3.6m.dylib                 0x0000000101aaa3d4 PyEval_EvalCode + 100
	24  libpython3.6m.dylib                 0x0000000101ae00f1 PyRun_FileExFlags + 209
	25  libpython3.6m.dylib                 0x0000000101adf8a3 PyRun_SimpleFileExFlags + 851
	26  libpython3.6m.dylib                 0x0000000101af870f Py_Main + 3535
	27  python                              0x00000001019d5df8 main + 232
	28  libdyld.dylib                       0x00007fff8c0cb5ad start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Abort trap: 6

私の環境だとなぜかElpyでC-c C-cだと実行できるが,シェルからだと起動できなかった.

解決は以下.
stackoverflow.com

import tkinter as tk
import pygame

root = tk.Tk()
root.title('hoge')

pygame.init()
pygame.mixer.init()
se = pygame.mixer.Sound('./se_file.wav')

def ring():
    se.play()

button = tk.Button(root, command=ring)
button.pack()

root.mainloop()

pygame.init()のまとまりをroot = tk.Tk()のまとまりの後に移動したら解決した.