OpenCVでイメージウィンドウを閉じられるようにする
能書き
OpenCVでイメージを表示した際に、面倒な落とし穴がある。
クローズボタンを押してしまうと処理がハングアップする場合があることだ。
上記回答ではプロパティを確認する方法が紹介されている。
ただ、毎度似たような処理を書くのは面倒に思える。
記事の目的
- ウィンドウの可視性プロパティを取得する際の挙動を追うこと
- もっと簡単かつ安全にウィンドウを表示する方法がないか検討すること
ドキュメントにはどのように書いてある?
この手の問題は普通はドキュメントに明記してあるものだ。
Parameters ...
See also setWindowProperty
書いてねぇ。
setWindowPropertyにもWindowPropertyFlagsにも目的の情報はなかった。
仕方ないので実装を覗く
cv::getWindowProperty @highgui/src/window.cpp#L90
cvGetWindowPropertyに主だった処理を委託している。
cvGetWindowProperty @highgui/src/window.cpp#L236
関連した部分を引用する。
/* return -1 if error */ CV_IMPL double cvGetWindowProperty(const char* name, int prop_id) { if (!name) return -1; switch(prop_id) { ... case CV_WND_PROP_VISIBLE: #if defined (HAVE_QT) return cvGetPropVisible_QT(name); #else return -1; #endif break; default: return -1; } }
Qtに委託しているのか。そういうことをドキュメントに書いておけよ。
cvGetPropVisible_QT @highgui/src/window_QT.cpp#L141
double cvGetPropVisible_QT(const char* name) { if (!guiMainThread) CV_Error( CV_StsNullPtr, "NULL guiReceiver (please create a window)" ); double result = 0; QMetaObject::invokeMethod(guiMainThread, "getWindowVisible", autoBlockingConnection(), Q_RETURN_ARG(double, result), Q_ARG(QString, QString(name))); return result; }
まずGUIスレッドが起動していないといけないようだ。
こいつについても調査したのだが、話がそれるのでここでは省略する。*1
少なくとも画像を表示しているときはスレッドは起動している。
よって例外が生じたときは『画像は閉じられている』と認識しても良い。
次に調査すべきは getWindowVisible だ。*2
GuiReceiver::getWindowVisible @highgui/src/window_Qt.cpp#L924
このコードは随分シンプルだ。
double GuiReceiver::getWindowVisible(QString name) { QPointer
w = icvFindWindowByName(name); if (!w) return 0; return (double) w->isVisible(); }
ここで isVisible の正体は QWidget::isVisible で、返り値は当然boolである。
可視性を調べたときの挙動まとめ
- バックエンドがQtでなく、調査不能である際は -1 を返す。
- guiMainThreadが起動されていないときは、CVError を投げる。
- 該当する名前のウィンドウがないときは、0 を返す。
- 該当する名前のウィンドウがある場合
- 開いているときは 1 を返す。
- 閉じているときは 0 を返す。
使いやすいラッパー関数を考える
単純に返り値や例外を制御するだけならば、次のようになるだろう。
import cv2
BackendError = type('BackendError', (Exception,), {})
def is_visible(winname):
try:
ret = cv2.getWindowProperty(
winname, cv2.WND_PROP_VISIBLE
)
if ret == -1:
raise BackendError('Use Qt as backend to check whether window is visible or not.')
return bool(ret)
except cv2.error:
return False
動作を確認するなかで、次の事実に気付いた。
- 明示的にデストロイしないと、ウィンドウは『見える』扱い。
- namedWindowを呼び出しただけでも『見える』扱い。
よって、is_visibleを単体で使うメリットは薄い。人間の直感に反する。
本当に使いやすいラッパー関数を考える
結局imshowをラップするのが分かりやすいのだろう。
import cv2
BackendError = type('BackendError', (Exception,), {})
def _is_visible(winname):
try:
ret = cv2.getWindowProperty(
winname, cv2.WND_PROP_VISIBLE
)
if ret == -1:
raise BackendError('Use Qt as backend to check whether window is visible or not.')
return bool(ret)
except cv2.error:
return False
ORD_ESCAPE = 0x1b
def closeable_imshow(winname, img, *, break_key=ORD_ESCAPE):
while True:
cv2.imshow(winname, img)
key = cv2.waitKey(10)
if key == break_key:
break
if not _is_visible(winname):
break
cv2.destroyWindow(winname)
良い感じ。
成果物
ブレークするための条件をもうちょっと詳細に設定できるようにしてみた。
- closeable_imshow(winname, img, *, break_keycode=0x1b)
- Parameters
- winname: str
- img: numpy.ndarray
- break_keycode (optional keyword)
ウィンドウを閉じるためのアスキーコードを指定する。コードの指定方法は整数値でも文字でも良いが、リストやタプルを用いて複数指定する場合はアスキーコードでなくてはならない。また'all'(全てのキー入力に反応)や'nothing'(全てのキータイプを無視)を指定することも出来る。
- Returns
- None
- Raises
- BackendError
調査に充分なバックエンド(Qt)がないとき - ValueError
break_keycodeを文字列で指定したが、その構成が誤っているとき - TypeError
break_keycodeの指定が誤っているとき
ソースコードはGistに上げておいた。cv_util.py · GitHub
Pythonを起動しただけでUnicodeDecodeError
能書き
表題のとおり。
何の気なしに対話環境を起動したら、次のようなエラーに見舞われた。
>python
Python 3.6.2 |Continuum Analytics, Inc.| (default, Jul 20 2017, 12:30:02) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
Failed calling sys.__interactivehook__
Traceback (most recent call last):
File "C:\...\site.py", line 418, in register_readline
readline.read_history_file(history)
File "C:\...\rlmain.py", line 166, in read_history_file
self.mode._history.read_history_file(filename)
File "C:\...\history.py", line 82, in read_history_file
for line in open(filename, 'r'):
UnicodeDecodeError: 'cp932' codec can't decode byte 0x99 in position 2111: illegal multibyte sequence
なにこれ?
解消
どうやら C:\Users\MyName\.python_history が影響しているようだ。
中身を覗いてみたところ、本当にただのヒストリだったので消してやった。*1
直前に minty で遊んでいたのが原因な気がする。
.python_historyって?
普通に使う分には気にしなくてよさそう。
*1:ゴリラ的解決。まあキャッシュのクリアって言い換えればいいのか。
Androidで長押し可能なカスタムViewを作る
能書き
ノリでAndroid開発を始めて二日目。
それっぽい挙動をするViewを作れたので記録しておく。
コードだけコピりたい人はこちら: Holdable button sample. · GitHub
なお、ド素人が書いたコードであると免責しておく。*1*2
.javaファイル
public class HoldableButton extends AppCompatButton {
private int delayMsec;
private int intervalMsec;
private MainTask mainTask;
private final Handler handler = new Handler();
private Runnable runnableCode;
//
//
public HoldableButton(Context context) {
this(context, null);
}
public HoldableButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public HoldableButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(
context.obtainStyledAttributes(
attrs, R.styleable.HoldableButton, defStyleAttr, 0
)
);
setMainTask(() -> {});
}
private void initAttrs(TypedArray typedArray) {
delayMsec = typedArray.getInteger(
R.styleable.HoldableButton_delay_msec, 400
);
intervalMsec = typedArray.getInteger(
R.styleable.HoldableButton_interval_msec, 100
);
typedArray.recycle();
}
private void initListener() {
setOnClickListener(view -> mainTask.doTask());
//
runnableCode = () -> {
mainTask.doTask();
handler.postDelayed(runnableCode, intervalMsec);
};
setOnTouchListener(
(view, motionEvent) -> {
switch(motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
handler.postDelayed(runnableCode, delayMsec);
break;
case MotionEvent.ACTION_UP:
handler.removeCallbacks(runnableCode);
view.performClick();
break;
default:
break;
}
return true;
}
);
}
//
public void setMainTask(MainTask mainTask) {
this.mainTask = mainTask;
initListener();
}
//
//
@FunctionalInterface
public interface MainTask {
void doTask();
}
}
果たすべきメインタスクを文字通りmainTaskとして保持している。
- タップが短いとき → mainTask.doTaskを一度だけ呼び出す。
- タップが長いとき → mainTask.doTaskをintervalMsecおきに呼び出す。
そしてタップが『長い』と判断する閾値がdelayMsecなのである。
これらのパラメータを扱いやすくするため、属性とすることにした。
.xmlファイル
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="HoldableButton">
<attr name="delay_msec" format="integer" />
<attr name="interval_msec" format="integer" />
</declare-styleable>
</resources>
正直今の段階では『なんとなく感』が強いので解説は避ける。
また、このままだとボタンが潰れて表示される。
三時間粘った末、解決策が発見できた。
You should explicitly setandroid:theme
. That theme should be derived fromWidget.AppCompat.Button
theme.
先人は偉大だ。
むすび
試行錯誤しながら、主に次のリンク先の情報を元に完成させることが出来た。
Python3.xで『スライドするイテレータ』を書いてみる (重複あり/なし)
能書き
次のようなイテレータが欲しくなったことはないだろうか。
>>> for e in ideal_it_A(range(6), 3):
... print(e)
...
(0, 1, 2)
(3, 4, 5)
>>>
>>> for e in ideal_it_B(range(6), 3):
... print(e)
...
(0, 1, 2)
(1, 2, 3)
(2, 3, 4)
(3, 4, 5)
これらを書いてみただけの話。Gist
重複のないイテレータ
出落ちのようだが、これについては公式リファレンスに言及がある。
引用元: 2. 組み込み関数 — Python 3.6.3 ドキュメント (太字は引用者)
zip(*iterables)
それぞれのイテラブルから要素を集めたイテレータを作ります。
(中略)
イテラブルの左から右への評価順序は保証されています。そのため zip(*[iter(s)]*n) を使ってデータ系列を長さ n のグループにクラスタリングするイディオムが使えます。これは、各出力タプルがイテレータを n 回呼び出した結果となるよう、 同じ イテレータを n 回繰り返します。これは入力を長さ n のチャンクに分割する効果があります。
よって、これをそのまま用いて次のように書けばよい。
def split_each(iterable, n):
return zip(
* [iter(iterable)]*n
)
重複のあるイテレータ
一方こちらのイテレータについては、リファレンスに言及がない。
最初は単純に考え、次のようなコードを書いてみた。
def slide_each_simple(iterable, n):
sequence = tuple(iterable)
return zip(
* [sequence[i:] for i in range(n)]
)
しかしこの方法は、与えられた全てのイテレータをタプルに置き換えてしまう。
意味合い的には分かりやすいが、実体化する時間的なコストがある。
そこで、次のような二つの代替関数を用意した。*1
from copy import copy
def slide_each_faster_A(iterable, n):
iterator = iter(iterable)
return zip(
* [
[copy(iterator), next(iterator)][0]
for _ in range(n)
]
)
def slide_each_faster_B(iterable, n):
def _func(iterator, n):
for _ in range(n):
yield copy(iterator)
next(iterator)
return zip(
*_func(iter(iterable), n)
)
後述するとおり上記の代替関数のパフォーマンスはさして変わらない。
好きな方を使うと良いだろう。後者の方が素直だが。*2
実行速度の計測/比較
ideal_it_Bを満たす三関数について、実行速度を比較してみた。*3
Gist | Iterator | Sequence | |
---|---|---|---|
List | Tuple | ||
simple | 23.01130 | 10.26225 | 7.27913 |
faster_A | 14.45048 | 13.33324 | 13.42435 |
faster_B | 14.48402 | 13.39973 | 13.36088 |
結果を見ると、必ずしもfaster_Xが速いというわけではないことがわかる。
simpleのボトルネックは、やはりイテレータのタプルへの置き換えのようだ。
次のようにエイリアスを決めておくと、ストレスなく利用できるかもしれない。
slide_each = slide_each_faster_A
slide_each_gen = slide_each_faster_A
slide_each_seq = slide_each_simple
悲しいオチ
書いた記事が間違っていると恥ずかしい。従って、必死で調べる。
その結果、自らのコーディングが車輪の再発明に過ぎないことを知るのだ。
These tools yield windows of items from an iterable. Return a sliding window of width n over the given iterable.
>>> from more_itertools import windowed
>>>
>>> for e in windowed(range(5), 3):
... print(e)
...
(0, 1, 2)
(1, 2, 3)
(2, 3, 4)
>>>
>>> for e in windowed(range(5), 3, step=2):
... print(e)
...
(0, 1, 2)
(2, 3, 4)
わあ便利。みなさんはこっちを使いましょう。pipで入るよ。
私のコードに優位性を見出すとしたら、ミニマムなことくらいだと思う。*4
追記:2018/02/01
タイムリーにも、同様の記事がQiitaにアップされていた。
このコードをちょいと書き換えて、more-itertoolsを含め再計測してみる。
Gist | Iterator | Sequence | |
---|---|---|---|
List | Tuple | ||
simple | 22.97496 | 10.17278 | 7.31506 |
faster_A | 14.70850 | 13.49781 | 13.45389 |
faster_B | 14.71984 | 13.70904 | 14.27782 |
quanon | 3.06136 | 2.77098 | 3.70850 |
windowed | 0.67819 | 0.38008 | 0.53994 |
まじか。5~6倍の違いは覚悟していたが、まさか数十倍の差があるとは。
逆に言えば高速化に関する良い題材となり得るので、今後解析しようと思う。
OpenCVの内部関数partitionをPython3.xで焼き直す
能書き
OpenCVについて以前調べているとき、内部関数partitionの存在を知った。
簡易なクラスタリングを行う汎用関数のようだ。
個人的には、こいつはとんでもなく使い勝手が良い関数だと思う。
ただし残念なことに、この関数はユーザに開放されていないのだ。*1
そいつを、自力で焼き直して解決してみた話。
こんなときにお勧め
- クラスタリングではあるが、問題が簡単なのでサクッと書きたい
- 同属条件がルールベースで表現でき、学習を求めていない
- クラスタ数が事前に予測できない (でもx-meansを使うほどでもない)
- ラムダ式を覚えたばかりで使いたくてたまらない
How to Use
大抵の読者にとって、『どのように実現するか』は関心の外にあるだろう。
差が1以内の要素を同一グループにする
連続する要素を同一のクラスタに振り分ける。
from partition import partition
my_list = [i for i in range(20)]
del my_list[3:6]
del my_list[4:8]
print(my_list) # [0, 1, 2, 6, 11, 12, 13, 14, 15, 16, 17, 18, 19]
part1 = partition(my_list, lambda x, y: abs(x-y) <= 1)
print(part1) # [0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2]
偶奇をグループ分けする
偶数/奇数にそれぞれ0/1のラベルを与える。
part2 = partition(my_list, lambda x, y: x % 2 == y % 2)
print(part2) # [0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
結果に応じて分割する
意外と煩雑な処理が必要なので、関数gen_groupを用意しておいた。
from partition import gen_group
for group in gen_group(my_list, part1):
print(group)
"""出力
[0, 1, 2]
[6]
[11, 12, 13, 14, 15, 16, 17, 18, 19]
"""
for group in gen_group(my_list, part2):
print(group)
"""出力
[0, 2, 6, 12, 14, 16, 18]
[1, 11, 13, 15, 17, 19]
"""
実装
本家のpartitionに倣って、UnionFind木を利用する。
自力で組むのも良い勉強になるだろうが...
ここではパフォーマンスを優先して既存のコードを利用する。*2
今回書いたpartition.pyは次のとおり。Gist
from itertools import product, groupby
import operator
from UnionFind import UnionFind
def _renumber(id_list):
id_set = set(id_list)
replace_dict = dict(zip(
sorted(list(id_set)),
[i for i, _ in enumerate(id_set)]
))
return [replace_dict[elem] for elem in id_list]
def partition(src_list, predicate_func):
src_len = len(src_list)
uf = UnionFind(src_len)
loop_obj = product(src_list, src_list)
for ij, (ei, ej) in enumerate(loop_obj):
i, j = divmod(ij, src_len)
if ei is ej:
continue
if not predicate_func(ei, ej):
continue
uf.union(i, j)
return _renumber(uf._id)
def gen_group(src_list, cluster_list):
groups = groupby(
sorted(
zip(src_list, cluster_list),
key=operator.itemgetter(-1)
),
key=operator.itemgetter(-1)
)
return (
[e[0] for e in elems] for _, elems in groups
)
この中で強いて解説するなら_renumber関数だろう。
この関数は野放図に振られたクラスタIDを、0からの連番に変更する。
>>> from partition import _renumber
>>> _renumber([0, 2, 4, 4, 6])
[0, 1, 2, 2, 3]
日頃行っているパズルのようなコーディングも、案外役に立つものだ。
おまけ - このモジュールを書いた経緯
もともと『近い点を同一視』する処理を実現するために書いた。
このように新たな発見があるものだから、teratailは面白い。*3
そこそこ便利なので、PyPIにアップする練習に使っても良いかもしれない。
どうせ既に似たようなモジュールがあるんだろうけど。
追記:2018/01/26
more_itertools.partitionという同様の関数が存在することが発覚。
ただし、これは2クラス分類しか出来ないようだ。セーフセーフ。
more_itertools.partition(pred, iterable)
Returns a 2-tuple of iterables derived from the input iterable. The first yields the items that have pred(item) == False. The second yields the items that have pred(item) == True.
Land of Lispを読んでいたのになぜかC++のコードを書いていた
前書き
悟りを得たくて、神の言語Lispに手を付けてみた。
もちろんここで紐解くのは、言わずと知れた聖典Land of Lispである。
- 作者: M.D. ConradBarski,Conrad Barski,川合史朗
- 出版社/メーカー: オライリージャパン
- 発売日: 2013/02/23
- メディア: 大型本
- 購入: 1人 クリック: 18回
- この商品を含むブログ (19件) を見る
この本はなかなか痛快で、他の言語のユーザを痛烈に皮肉っている。*1
Lispのシンプルさ*2の主張として、次のような例が取り上げられている(p.20)。
((foo<bar>)*(g++)).baz(!&qux::zip->ding())このコードはC++のシンタックスに従っている。意図を強調するために、C++の中でも特に他の言語とは違う変てこりんなシンタックスをてんこ盛りにしてみた。
[中略]
もちろんこのC++プログラムは意味を持っている。このコードをC++のプログラムに埋め込めば、それはコンピュータに何か動作をさせるだろう。
...ほんとにこれ動くの?というわけで、試してみる。
まずは解析
どこがどう結合するのか、適当な結合表を見ながら考えてみる。
そのうち、今回の式に登場する演算子だけをまとめると次のようになる。
優先順位 | 演算子 | 名称 | 結合規則 |
---|---|---|---|
1 | :: | スコープ解決演算子 | 左から右 |
2 | a++ | 後置インクリメント演算子 | |
a() | 関数呼び出し演算子 | ||
. | メンバアクセス演算子(ドット演算子) | ||
-> | メンバアクセス演算子(アロー演算子) | ||
3 | ! | 論理否定演算子 | 右から左 |
&a | 単項アドレス演算子 | ||
5 | a*b | 乗算演算子 | 左から右 |
項目は C++ Operator Precedence - cppreference.com を、
日本語名称は C++ の演算子、優先順位と結合規則 を参照した。
さて、この表に基づくと、次の二式は同値である。
((foo<bar>)*(g++)).baz(!&qux::zip->ding())
((foo<bar>)*(g++)).baz(!(&(((qux::zip)->ding()))))
後者めっちゃくちゃ汚いな。まあ、これで検証に踏むべき手順は見えた。
実際に書いてみる:part1
まずは、bazの実引数部分について考えてみよう。
以下のコードは、全てG++でコンパイルが通る。*3
ただし、-Wunused-value(評価されない値)ワーニングは出るけどね。*4
quxスコープの中に変数zipを作る
zipの型は何でもいいので、空のクラスを作った。
class Zip{};
namespace qux {
Zip zip;
}
int main(void) {
qux::zip;
}
zipからアロー演算子でdingを参照する
アロー演算子を適用出来るということは、zipがポインタであることを意味する。
それを踏まえて、先ほどのコードから少し宣言部分も修正してある。
class Zip{
public:
void ding(void) {
}
} zip_;
namespace qux {
Zip *zip = &zip_;
}
int main(void) {
qux::zip->ding();
}
dingの戻り値のアドレスを解決する
dingは実体を持つ何らかのオブジェクトを返す必要がある。
手を抜くとしたら自分自身の参照を返すのが一番楽だろう。
class Zip{
public:
Zip& ding(void) {
return *this;
}
} zip_;
namespace qux {
Zip *zip = &zip_;
}
int main(void) {
&qux::zip->ding();
}
得られたアドレスの論理否定を取る
これに関しては特に書き換えるべき箇所はない。
また、zip_のアドレスがヌルであり得ない以上、必ず式の値はfalseになる。
class Zip{
public:
Zip& ding(void) {
return *this;
}
} zip_;
namespace qux {
Zip *zip = &zip_;
}
int main(void) {
!&qux::zip->ding();
}
実際に書いてみる:part2
次は、bazをメンバ関数として持っているオブジェクトを書く。
面倒なので、クラスZipをここでも利用してやろう。
テンプレート変数を用意する
テンプレートクラス/関数はよく見かけるが、テンプレート変数はあまり見ない。
なぜなら、この要素はC++14以降に追加されたからである。
変に説明するのも面倒なので、有益なリンクを貼っておく。
変数テンプレート - cpprefjp C++日本語リファレンス
これを踏まえると、次のように表現できるだろう。
class Zip{
};
using bar = Zip;
template<typename T> T foo;
int main(void) {
foo<bar>;
}
エイリアスとは言え、クラス名がパスカルケースでないのは大分気持ち悪い。
まあ、次のように書いてもいいのだが。これはこれはなんか嫌だ。
class Zip{
};
constexpr int bar = 0;
template<int B> Zip foo;
int main(void) {
foo<bar>;
}
gを定義する
説明不要だろう... と言いたいが。せっかくだ、gもZip型にしてしまおう。
この場合、後置インクリメントを自前でオーバーロードしないといけない。
今回初めて知ったのだが、こいつを定義する場合、ダミーの引数が必要だと言う。
C++マニアック - オペレータのオーバーロード | C++入門 C++言語講座
これは言語仕様上の欠陥じゃないか?C++ともあろう人が...
とりあえず組む。実用は度外視しているので、自身の参照を返せばよいだろう。
コンマ演算子はどんな状況でも使えるので、こういうときは重宝する。
class Zip{
public:
Zip& operator++(int) {
return *this;
}
} g;
using bar = Zip;
template<typename T> T foo;
int main(void) {
foo<bar>, g++;
}
アスタリスク二項演算子を定義する
直前の内容を踏まえれば、今度こそ説明不要だろう。
class Zip{
public:
Zip& operator++(int) {
return *this;
}
Zip& operator*(Zip&) {
return *this;
}
} g;
using bar = Zip;
template<typename T> T foo;
int main(void) {
(foo<bar>)*(g++);
}
実際に書いてみる:マージ
part1とpart2の流れを汲むと、次のように問題が実現可能であることがわかる。
ただし、Zipに論理値を引数に取るメンバ関数bazを追加する必要がある。
class Zip{
public:
Zip& ding(void) {
return *this;
}
void baz(bool) {
}
Zip& operator++(int) {
return *this;
}
Zip& operator*(Zip&) {
return *this;
}
} zip_, g;
namespace qux {
Zip *zip = &zip_;
}
using bar = Zip;
template<typename T> T foo;
int main(void) {
((foo<bar>)*(g++)).baz(!&qux::zip->ding());
}
bazの中身が空だと少し悲しい。
せっかく引数を取っているのだから、華の出力部分を任せよう。
#include <iostream>
class Zip{
public:
Zip& ding(void) {
return *this;
}
void baz(bool arg) {
if(arg) std::cout << "Lispは神の言語\n";
}
Zip& operator++(int) {
return *this;
}
Zip& operator*(Zip&) {
return *this;
}
} zip_, g;
namespace qux {
Zip *zip = &zip_;
}
using bar = Zip;
template<typename T> T foo;
int main(void) {
((foo<bar>)*(g++)).baz(!&qux::zip->ding());
}
いざ動かしてみると... 何も表示されない。
そりゃそうだ。bazの引数は絶対にfalseになるんだから。
結論
前述の式は、確かに記述可能であったし、警告も何もない。
ただし、有用に利用出来ることは断じてないだろう。
それでも俺は無理矢理動かす
bazの中で論理値を反転させてしまうのが早いのだが。
せっかくなのでオーバーロードを悪用してみる。
#include <iostream>
class FooBar {
public:
bool operator&(void) {
return false;
}
} foobar;
class Zip{
public:
FooBar& ding(void) {
return foobar;
}
void baz(bool arg) {
if(arg) std::cout << "Lispは神の言語\n";
}
Zip& operator++(int) {
return *this;
}
Zip& operator*(Zip&) {
return *this;
}
} zip_, g;
namespace qux {
Zip *zip = &zip_;
}
using bar = Zip;
template<typename T> T foo;
int main(void) {
((foo<bar>)*(g++)).baz(!&qux::zip->ding());
}
Pythonのモジュールキャッシュ作成/使用条件を確認してみる
前書き
Pythonをいじっているとにょきにょき発生する__pychache__。
内部にはコンパイル済みモジュール.pycが格納される。
リファレンスには、次のような記述がある。
ちょっと変形すると、次のような感じだ。(引用表記してるけどかなり改変してるよ)
Pythonがキャッシュをチェックしない場合
- コマンドラインから直接モジュールが読み込まれた場合
常に再コンパイルされ、結果を保存することはない。- ソース・モジュールのない場合
コンパイル済みモジュールはソース・ディレクトリになくてはならない。
これを実際に試してみるだけの記事。
Windows前提の記事だけど、Macユーザもなんとなくわかるよね?
(逆のことをMacユーザに言われたらキレる自信がある)
本文
次のように意味のないコードを書いた。これでも立派なモジュールである。
print('hogee')
そしてディレクトリ階層は次のとおり。
hoge_py/
└ hoge.py
階層と呼ぶのもおこがましいが、ここらへんは明確にしないといけない。
コマンドラインから直接モジュールが読み込まれた場合
つまりこういうこと。もちろん見やすいように成形はしてあるが。
C:\...\hoge_py>dir
2017/10/30 17:09 <dir> .
2017/10/30 17:09 <dir> ..
2017/10/30 17:20 14 hoge.py
C:\...\hoge_py>python hoge.py
hogee
C:\...\hoge_py>dir
2017/10/30 17:09 <dir> .
2017/10/30 17:09 <dir> ..
2017/10/30 17:20 14 hoge.py
見てわかるように、実行前後での変化はない。
逆に、コマンドラインから直接モジュールが読み込まれない場合とは?
こういうことである。もちろんこちらも成形済み。
C:\...\hoge_py>dir
2017/10/30 17:09 <dir> .
2017/10/30 17:09 <dir> ..
2017/10/30 17:20 14 hoge.py
C:\...\hoge_py>python
>>> import hoge
hogee
>>> exit()
C:\...\hoge_py>dir
2017/10/30 17:41 <dir> .
2017/10/30 17:41 <dir> ..
2017/10/30 17:20 14 hoge.py
2017/10/30 17:41 <dir> __pycache__
C:\Users\...\hoge_py>dir __pycache__
2017/10/30 17:41 <dir> .
2017/10/30 17:41 <dir> ..
2017/10/30 17:41 151 hoge.cpython-36.pyc
確かにキャッシュファイルが作られているのがわかる。
また、ファイル名からコンパイル環境がわかるのも便利だ。
ソース・モジュールのない場合
さきほどの実験の続き。hoge.pyを消してやると...
C:\...\hoge_py>del hoge.py
C:\...\hoge_py>dir
2017/10/30 17:41 <dir> .
2017/10/30 17:41 <dir> ..
2017/10/30 17:41 <dir> __pycache__
C:\...\hoge_py>python
>>> import hoge
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'hoge'
>>> exit()
C:\...\hoge_py>dir
2017/10/30 17:41 <dir> .
2017/10/30 17:41 <dir> ..
2017/10/30 17:41 <dir> __pycache__
当然ながら、hogeモジュールをインポートすることは出来ない。
ここで、先ほど生成されたキャッシュを次の手順で利用してみよう。
このように、モジュールをインポート出来るようになる。
C:\...\hoge_py>python
>>> import hoge
hogee
バージョンがファイル名に含まれる利点が失われているのが少々残念である。
なお、このときのディレクトリ階層は次のようになっている。
hoge_py/
└ hoge.pyc
...うん。階層と呼ぶのも難だが、説明を明確にするには必要なのだ。
おまけ - キャッシュを利用可能にするバッチ
最後は簡単な説明で済ませてしまったが、これをCUIで書くと次のようになる。
rem rm *.py
move .\__pycache__\* .\
rd .\__pycache__ /q
ren *.pyc ????????????????.pyc
remはコメントアウトの意味だ。
一行目は完全に実装を隠したいデストロイヤー向けなのである。
なお、リネームに随分回りくどい方法を用いているのは理由がある。
説明するのが面倒なので、サンプルとリンクだけ置いておく。
上手くいきそうでいかない例
C:\...\hoge_py\__pycache__>dir
2017/10/30 20:03 <dir> .
2017/10/30 20:03 <dir> ..
2017/10/30 19:55 151 hoge.cpython-36.pyc
C:\...\hoge_py\__pycache__>dir *.cpython-36.pyc
2017/10/30 19:55 151 hoge.cpython-36.pyc
C:\...\hoge_py\__pycache__>ren *.cpython-36.pyc *.pyc
C:\...\hoge_py\__pycache__>dir
2017/10/30 20:03 <dir> .
2017/10/30 20:03 <dir> ..
2017/10/30 19:55 151 hoge.cpython-36.pyc
とりあえず思い通りの結果が出る例
C:\...\hoge_py>dir
2017/10/30 20:03 <dir> .
2017/10/30 20:03 <dir> ..
2017/10/30 19:55 132 hoge.cpython-36.pyc
C:\...\hoge_py>ren *.cpython-36.pyc ????????????????.pyc
C:\...\hoge_py>dir
2017/10/30 21:56 <dir> .
2017/10/30 21:56 <dir> ..
2017/10/30 19:55 132 hoge.pyc
おまけ - ソースを置き換えるスクリプト
配布目的ならsetup.pyを書けばよいので、これはただの嫌がらせである。
むしゃくしゃしたときに実装をすべて隠蔽してやろう。
import os, sys, glob, importlib, shutil
from distutils import dir_util
# 自分以外の.pyファイルをすべて取得
py_files = glob.glob('*.py')
py_files.remove(sys.argv[0])
# 片っ端からインポートして消す
for py_file in py_files:
importlib.__import__(
os.path.splitext(os.path.basename(py_file))[0]
)
os.remove(py_file)
# pycacheの中身を今のディレクトリに移す
dir_util.copy_tree('./__pycache__', './')
shutil.rmtree('./__pycache__')
# pycの中身をリネーム
pyc_files = glob.glob('*.pyc')
for pyc_file in pyc_files:
basename, ext = os.path.splitext(os.path.basename(pyc_file))
os.rename(pyc_file, basename.split('.')[0] + ext)
書き捨てなのでぐちゃぐちゃなコードだ。特にインポートの多さ。*1
とにかく、これを使うと次のように嫌がらせが出来る。
C:\...\hoge_py>dir
2017/10/30 21:46 <dir> .
2017/10/30 21:46 <dir> ..
2017/10/30 21:39 0 fuga.py
2017/10/30 21:39 0 hoge.py
2017/10/30 21:39 0 piyo.py
2017/10/30 21:42 726 replace_source_to_cache.py
C:\...\hoge_py>python replace_source_to_cache.py
C:\...\hoge_py>dir
2017/10/30 21:46 <dir> .
2017/10/30 21:46 <dir> ..
2017/10/30 21:46 132 fuga.pyc
2017/10/30 21:46 132 hoge.pyc
2017/10/30 21:46 132 piyo.pyc
2017/10/30 21:42 726 replace_source_to_cache.py
追記:組み込みモジュールpy_compileを使うと、もっと簡単に書ける。
import py_compile
py_compile.compile('hoge.py', cfile='hoge.pyc')
.pycファイルの移動をサボれるだけだが。
*1:Python3.4以降ならpathlib使うべき
クソみたいなPythonワンライナーを書いてみた
前書き
あるときふと思った。クソコードを書きたい。
コードと動作
コードは次の通り。素因数を表示するものだ。
一応Python3だが、print関数をprint文に置き換えればPython2でも動作する。
print((lambda f: (lambda n: f(f, [], n, 2)))(lambda f, r, n, i: r if n == 1 else (r.append(i) or f(f, r, n/i, i)) if n % i == 0 else f(f, r, n, i+1))(int(input())))
実行とその結果は次のようである。
>python -c "print((lambda f: (lambda n: f(f, [], n, 2)))(lambda f, r, n, i: r if n == 1 else (r.append(i) or f(f, r, n/i, i)) if n % i == 0 else f(f, r, n, i+1))(int(input())))"
100
[2, 2, 5, 5]
我ながら満足のクソっぷりである。
改善したい点を挙げるとしたら、リストを使いまわしているところだろうか。
ラムダ式を上手くジェネレータのように活用できないだろうか。
一応解説
解説というか、見やすく分解しただけだが。
print(
# 再帰可能にする工夫と、高階関数としての役割
(lambda func:
(lambda num:
func(func, [], num, 2)
)
)
# 素因数分解をつかさどる部分
(lambda func, result, num, i:
# 終了条件と返り値
result if num == 1
# i が num の約数のとき
else (result.append(i) or func(func, result, num/i, i)) if num % i == 0
# i が num の約数でないとき
else func(func, result, num, i+1)
)
# 実際に関数に与える引数
(int(input()))
)
ワンラインの拘りなしにこれを書くとこのようになる。
def higher_order(func):
return lambda num: func(func, [], num, 2)
@higher_order
def ret_factor(func, result, num, i):
if num == 1:
return result
if num % i == 0:
result.append(i)
return func(func, result, num/i, i)
else:
return func(func, result, num, i+1)
print(
ret_factor(int(input()))
)
そもそもラムダ式がわからん。デコレータも知らん。という方はこちら。
def ret_factor(func, result, num, i):
if num == 1:
return result
if num % i == 0:
result.append(i)
return func(func, result, num/i, i)
else:
return func(func, result, num, i+1)
print(
ret_factor(func=ret_factor, result=[], num=int(input()), i=2)
)
普通に書くなら
ラムダ式での再帰のために、第一引数に自身を取る冗長な構造にしている。
こちらのページが非常に参考になった。
普通に書くなら、次のようになる。
def ret_factor(result, num, i):
if num == 1:
return result
if num % i == 0:
result.append(i)
return ret_factor(result, num/i, i)
else:
return ret_factor(result, num, i+1)
また、リストに値を格納して最後に返すのは、大抵無駄だ。
このようなときには、ジェネレータを使うべきである。
def ret_factor(num, i):
if num == 1:
return
if num % i == 0:
yield i
yield from ret_factor(num/i, i)
else:
yield from ret_factor(num, i+1)
本当はこれをワンラインにしたいのだが...
いかんせん終了時の処理を上手く表現できないのだ。
その後の足掻き
print(
list(
# 再帰可能にする工夫と、高階関数としての役割
(lambda func:
(lambda num:
func(func, num, 2)
)
)
# 素因数分解をつかさどる部分
(lambda func, num, i:
# 終了条件
None if num == 1
# i が num の約数のとき
else ((yield i), (yield from func(func, num/i, i))) if num % i == 0
# i が num の約数でないとき
else (yield from func(func, num, i+1))
)
# 実際に関数に与える引数
(int(input()))
)
)
このようにしたら出来た。達成感より先に気持ち悪さがあるが。
これはつまり、次のように書けるということなのだ...
>>> def func(num):
... ((yield num), (yield from func(num-1))) if num > 0 else None
...
>>> list(func(10))
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
なんとも恐ろしい。
さらに改造
例えば、次のように改造できる。
print(
(lambda max: [m for m in range(2, max) if len(
# 再帰可能にする工夫と、高階関数としての役割
(lambda func:
(lambda num:
func(func, [], num, 2)
)
)
# 素因数分解をつかさどる部分
(lambda func, result, num, i:
# 終了条件と返り値
result if num == 1
# i が num の約数のとき
else (result.append(i) or func(func, result, num/i, i)) if num % i == 0
# i が num の約数でないとき
else func(func, result, num, i+1)
)
# 実際に関数に与える引数
(m)
# 約数(1を除く)が1つだったら素数
) == 1])
# 求める素数の上限
(int(input()))
)
改行と無駄な空白を潰すとこんな感じ。
print((lambda max: [m for m in range(2, max) if len((lambda func: (lambda num: func(func, [], num, 2)))(lambda func, result, num, i: result if num == 1 else (result.append(i) or func(func, result, num/i, i)) if num % i == 0 else func(func, result, num, i+1))(m)) == 1])(int(input())))
入力された数値未満の素数リストを出力するワンライナーだ。
しかし、1000ほどの大きな値を入れるとRecursionErrorで落ちる。
末尾再帰最適化で解消できるはずだが、まあそこまではしなくてよいだろう。
一応参考までに。
さすがにこれも込みでワンライナーにするのは骨だ。
晒してみた
ImportError: cannot import nameにいじめられた話
前書き
ImportErrorの中で、頻出し、即座に解消出来るのはこのようなものかと思う。
>>> import hoge Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named 'hoge'
おおかたモジュール名を間違えたか、階層が異なるかのいずれかだろう。
しかし、今回私が遭遇したのは次のようなエラーだった。
>>> import sys >>> sys.path.append('hoge/') >>> from fuga import piyo Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: cannot import name 'piyo'
私はミスタイプもしていないし、何度確認しても階層hoge/fuga/piyoであった。
いったいなぜエラーが出たのか?どのようにして解決できるのか?
(熟練したPythonistaにとってはどっちもつまらないエラーかもしれないが)
具体的なコードと不可解な解決
「多分一般的なエラーだよ」と言いたいがために、hogeとかを使ってみた。
...が。もし一般的じゃなかったら恥ずかしいので、実際のコードを貼っておく。
>>> import sys >>> sys.path.append('C:/[中略]/TensorFlow/models/') >>> from object_detection.protos import hyperparams_pb2 Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: cannot import name 'hyperparams_pb2'
こんなコードだ。また、次のように書いたらエラーは解消された。
>>> import sys >>> sys.path.append('C:/[中略]/TensorFlow/') >>> from models.object_detection.protos import hyperparams_pb2
ダメ元で試してみたのに成功してしまったパターンの典型例である。
しかし、「よくわからんが解決」というのが一番気に食わないので粘った*1。
根本的原因 (推測だけど)
piyo.__path__を使えば、importしたパッケージpiyoのパスを知ることが出来る。
これを用いて、パッケージが本当はどこにあるのか調査してみよう。
# 上手くいくとき >>> import sys >>> sys.path.append('C:/[中略]/TensorFlow/') >>> from models import object_detection >>> object_detection.__path__ ['C:/[中略]\\models\\object_detection]
# 上手くいかないとき >>> import sys >>> sys.path.append('C:/[中略]/TensorFlow/models/') >>> import object_detection >>> object_detection.__path__ ['C:\\[中略]\\object_detection-0.1-py3.5.egg\\object_detection']
どうやら参照先が異なっているようだ。
このミスリードを招いているディレクトリを覗いてみたいと思う。
eggに関する解説は読み飛ばしてしまったが、その本質はzipであるらしい。
拡張子をzipにリネームすると、普通に解凍することが出来た。
そして肝心の中身がこちら。
どうやら、前なんとなくビルドしたデータがゴミになって残っていたらしい。
後書き
インポート系のエラーに遭遇したときは、その実態を覗いてみるとよさそうだ。
また、当たり前のことなのかもしれないが...
なんとなくビルドするのはやめよう
クソみたいな結論に至ってしまった。
追記:2017/07/05
なんか、タイムリーに似たような情報があがっていた。
*1:こういうのを許容できないから作業が遅い
TensorFlowのrestoreで、NotFoundErrorにいじめられた話
前書き
TensorFlowで学習済みモデルを使って遊ぼうとしたときに躓いたのでメモ。
ここに学習済みパラメータと、その他情報が置いてある。
生じた問題
次のコードで問題が生じた。
import tensorflow as tf
from tensorflow.contrib import slim
from tensorflow.contrib.slim.nets import resnet_v1
#
# Construct Network
inputs = tf.placeholder(tf.float32, shape=[None, 224, 224, 3])
net, end_points = resnet_v1.resnet_v1_50(inputs=inputs)
#
# Read ckpt File
ckpt_filename = "C:/[中略]/resnet_v1_50.ckpt"
with tf.Session() as sess:
saver = tf.train.Saver()
saver.restore(sess, ckpt_filename)
エラーは次の通りである。
tensorflow.python.framework.errors_impl.NotFoundError: Tensor name "resnet_v1_50/conv1/biases" not found in checkpoint files C:/[中略]/resnet_v1_50.ckpt
NotFoundError (see above for traceback): Tensor name "resnet_v1_50/conv1/biases" not found in checkpoint files C:/[中略]/resnet_v1_50.ckpt
かなり省略してあるが、結局必要なデータが見つからんというエラーである。
なお、上記エラーが発生したときも、ckptファイル自体は読み込めている。
かなりの人が躓いているらしく、ネット上でも悲痛な声が散見された。
エラーの原因
Githubのイシューを漁った結果、次のようなスレッドが見つかった。
sugada氏のコード片を見る限り、arg_scopeが解決の鍵のようだ。
arg_scopeはcontrib/framework/python/ops/arg_scope.pyで定義されていて、
Allows one to define models much more compactly by eliminating boilerplate code. This is accomplished through the use of argument scoping (arg_scope).
と書かれている。(たぶん)モジュール性を高めるための工夫らしい。
解決した方法
さきほどのコードを、次のように改変する。
import tensorflow as tf
from tensorflow.contrib import slim
from tensorflow.contrib.slim.nets import resnet_v1
#
# Construct Network
inputs = tf.placeholder(tf.float32, shape=[None, 224, 224, 3])
arg_scope = resnet_v1.resnet_arg_scope()
with slim.arg_scope(arg_scope):
net, end_points = resnet_v1.resnet_v1_50(inputs=inputs)
#
# Read ckpt File
ckpt_filename = "C:/[中略]/resnet_v1_50.ckpt"
with tf.Session() as sess:
saver = tf.train.Saver()
saver.restore(sess, ckpt_filename)
ネットワークモデルを読み取る際に、このような工夫が必要だった。
ここではResNetを用いたが、他のモデルも同様である。
実際のソースコードを見ると、それっぽい関数はすぐ見つかる。
追記 (2017/07/19)
次のページに、この記事へのリンクが張られていた。
TensorFlowのセッション内でrestoreする際に起こるエラーについて | teratail
ここで『どうも、管理人です』と颯爽と回答できればかっこよかったなぁ...
結局、よくわからないままに解消できたらしい。
Jupyter上でやっているのですが、全てを1つのセルにまとめて実行していたことが原因のようでした。
私もJupyterにいじめられたことがあるので... 苦手意識が更に高まる。