クーの自由研究

大きな海は知らないけれど、空(宇宙)の深層を学習します。

Atomで夜ごと心から辟易する母なるかえるのために

今回はデバッグに関するお題です

こんにちは、こんばんわ、かえるのクーです。
危険な音」をきいて暗黒面に堕ちましたが、生還してきました。

f:id:np2LKoo:20171015125120p:plain f:id:np2LKoo:20171015125128p:plain

へんなタイトルですみません。(細かすぎて伝わらないネタ系です)

Atomマシンでのデバッグをなんとかする

Atomマシンで機械学習の勉強ををされている方も多いと思いますが、いかんせん非力です。遅いだけなら待っているだけでいいのですが、タイムアウトが多発してデバックになりません。
タイムアウトを伸ばすとクリックするごとに10分以上まつはめになります。
お気楽なボクでもこれには耐えられません。

f:id:np2LKoo:20171015140601p:plain

そこで、デバック用に変数の内容をレポートするロジックをつくりました。
Atom機でデバッグタイムアウトに悩む、同志のかえるさんやおたまじゃくしさんのお役にたてるかもしれないので、恥ずかしいソースながら貼ってみたいと思います。
汎用的に作ったつもりですが、今調整しているプログラムがメインターゲットなので、機能の過不足はあるかもしれません。
用途に適さない方は、いろいろ改変してお使いください。

こんな方におすすめ

Atomデバッグに悩む方
デバッグタイムアウトになり、変数の内容が見れない方
・多量の配列や変数の内容の概要を確認したい方
・配列を多次元に掘り下げ、収集がつかなくなったしまった方

主作用(機能と効用)

機能は見てのとおり、変数を引数に入れて呼ぶだけなシンプルなものです。
工夫したところは、以下の感じです。

・変数をセットしただけで "変数=" の部分を表示する。
・変数の型を表示する。
・配列はPytorch「風」に配列の大きさや内容を示す。
・多量のデータを扱うことがあるので、配列はデータの最初と最後くらいだけを表示する。

副作用

チェック機能やデバッグ機能に頼りすぎると、いきあたりばったりのコーディングをする生活習慣に陥る可能性があります。過剰摂取と長期服用による依存には十分ご注意ください。
日頃より、シンプルな設計とリファクタリングを怠らず、デバッグ機能に頼るのは必要最小限になるよう、心がけましょう。

有効成分

inspect:コードの調査を行うためのライブラリです。
re:正規表現を使えるようにするライブラリです。。
numpy:標準的な各種処方を行うライブラリです。

用法と用量

Atom、(もしくはCeleron,Semron)機以外の高スペックCPUの方はご利用に注意ください。おそらく、標準のデバッガをうまく使用したほうが幸せと思われます。


なお、Pytorchについてはガリガリ (id:s0sem0y)様の記事を読ませていただいております。わかりやすい説明に感謝です。

(あくまで個人の見解です)

ボクはこの機能でかなり(というか超絶)デバッグが捗るようになりました。
Atom機でPythonデバッグに悩んでいるすべての方におすすめします。

ソース

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
import inspect
import re


class KooChecker():
def __init__(self):
pass

def rpt_obj(self, *args):
out = ''

back_frame = inspect.currentframe().f_back
back_code = inspect.getframeinfo(back_frame).code_context[0].replace(" ", "")
pos = back_code.find("#")
if pos != -1:
back_code = back_code[:pos]
arg = re.search(r"\((.+)\)", back_code).group(1)
# 呼び出し元関数の引数の変数名を取得。
arglist = arg.split(',')
ii = 0
for obj in args:
target = arglist[ii]
ii += 1
if isinstance(obj, bool) or \
isinstance(obj, int) or \
isinstance(obj, float) or \
isinstance(obj, complex):
out += '|' + target + ' = ' + str(obj) + ' ' + self.chktype(obj)
elif isinstance(obj, set) or \
isinstance(obj, frozenset):
out += '|' + target + ' = ' + self.rptset(obj) + ' ' + self.chktype(obj)
elif isinstance(obj, str):
out += '|' + target + ' = ' + self.rptstr(obj) + ' ' + self.chktype(obj)
elif isinstance(obj, bytes) or\
isinstance(obj, bytearray):
out += '|' + target + ' = ' + self.rptbytes(obj) + ' ' + self.chktype(obj)
elif isinstance(obj, list) or \
isinstance(obj, tuple) or \
isinstance(obj, np.ndarray):
out += '|' + target + ' = ' + self.rptlist([], obj) + '\n' + self.chktype(obj)
elif isinstance(obj, dict):
out += '|' + target + ' = ' + str(obj) + '\n' + self.chktype(obj)
else:
out += '|' + target + " =(DisplayNotImpliment) " +self.chktype(obj)
print(out)

def rptstr(self, obj):
strlen = len(obj)
# 表示内容を40文字までに抑制する
if strlen <= 40:
rtn = '"' + obj + '"'
else:
rtn = '"' + obj[0:20] + '...' + obj[strlen - 20:strlen] + '"' + "(len=" + str(strlen) + ")"
return rtn

def rptbytes(self, obj):
byteslen = len(obj)
# 表示内容を40byteまでに抑制する
if byteslen <= 40:
rtn = str(obj)
else:
rtn = str(obj[0:20]) + '...' + str(obj[byteslen - 20:byteslen]) + "(len=" + str(byteslen) + ")"
return rtn

def rptset(self, obj):
setlen = len(obj)
# 表示内容を20項目までに抑制する
if setlen <= 20:
rtn = str(obj)
else:
setstr = str(obj)
ss = (setstr.rsplit(",", setlen - 10))[0]
se = (setstr.split(",", setlen - 10))[-1]
rtn = ss + ', ...' + se + "(len=" + str(setlen) + ")"
return rtn

def rptlist(self, arglevel, obj):
out = ''
datalen = len(obj)
if datalen == 0:
out = '[]'
elif datalen <= 8:
for i in range(datalen):
if isinstance(obj[i], list) or \
isinstance(obj[i], tuple) or \
isinstance(obj[i], np.ndarray):
level = arglevel.copy()
level.append(i)
out += self.rptlist(level, obj[i])
else:
out += str(obj[i]) + ' '
else:
for i in (0, 1, 2, 3, -4, -3, -2, -1):
if isinstance(obj[i], list) or \
isinstance(obj[i], tuple) or \
isinstance(obj[i], np.ndarray):
if i in (0, 1, -2, -1):
level = arglevel.copy()
if i >= 0:
level.append(i)
else:
level.append(datalen + i)
out += self.rptlist(level, obj[i])
if i != -1:
out += '\n'
elif i in (2, 3, -4, -3):
pass
if i == 2:
out += '...\n'
else:
if i == 0:
out += str(arglevel) + '= '
out += str(obj[i]) + ' '
if i == 3:
out += ' ... '
elif i == -1:
out += '(len=' + str(datalen) + ')'
return out

def chktype(self, obj):
rtn = ''
if isinstance(obj, list) or \
isinstance(obj, tuple) or \
isinstance(obj, np.ndarray):
if len(obj) == 0:
rtn = self.chktype_name(obj) + ':0'
else:
rtn = self.chktype_name(obj) + ':' + str(len(obj)) + '*' + self.chktype(obj[-1])
else:
rtn = self.chktype_name(obj)
return rtn

def chktype_name(self, obj):
name = str(type(obj)).replace('<class ', '').replace('>', '').replace("'", "")
name = '<' + name + '>'
return name

用法

if __name__ == '__main__':
kc = KooChecker()
a = 1
b = 12.56
c = False
d = 2 + 3j
e1 = ''
e2 = "ABCDE123456" * 100
f1 = b''
f2 = b'A' * 100
g = bytearray(b'ABC' * 100)
h = set(range(100))
i = frozenset(range(100))
j1 = []
j2 = [range(100)]
j3 = [[range(10)],[range(20)],[range(30)]]
j4 = list(np.arange(10 ** 4).reshape((10, 10, 10, 10)))
j5 = [[list(np.arange(10 ** 2).reshape((10, 10)))] * 10] * 10
k1 = ()
k2 = (1,2,3,4,5,6,7,8,9,0)
k3 = (((1,2,3),(1,2,3),(1,2,3),(1,2,3),(1,2,3),(1,2,3),(1,2,3),(1,2,3),(1,2,3)) * 10)
l = {'one': 1, 'two': 2, 'three': 3}
kc.rpt_obj(a,b,c,d,e1,e2)
kc.rpt_obj(f1,f2,g,h)
kc.rpt_obj(i)
kc.rpt_obj(j1)
kc.rpt_obj(j2)
kc.rpt_obj(j3)
kc.rpt_obj(j4)
kc.rpt_obj(j5)
kc.rpt_obj(k1)
kc.rpt_obj(k2)
kc.rpt_obj(k3)
kc.rpt_obj(l)
kc.rpt_obj({'key': 100})

コンソール出力

|a = 1 <int>|b = 12.56 <float>|c = False <bool>|d = (2+3j) <complex>|e1 = "" <str>|e2 = "ABCDE123456ABCDE1234...CDE123456ABCDE123456"(len=1100) <str>
|f1 = b'' <bytes>|f2 = b'AAAAAAAAAAAAAAAAAAAA'...b'AAAAAAAAAAAAAAAAAAAA'(len=100) <bytes>|g = bytearray(b'ABCABCABCABCABCABCAB')...bytearray(b'BCABCABCABCABCABCABC')(len=300) <bytearray>|h = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ... 90, 91, 92, 93, 94, 95, 96, 97, 98, 99}(len=100) <set>
|i = frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ... 90, 91, 92, 93, 94, 95, 96, 97, 98, 99})(len=100) <frozenset>
|j1 = []
<list>:0
|j2 = range(0, 100)
<list>:1*<range>
|j3 = range(0, 10) range(0, 20) range(0, 30)
<list>:3*<list>:1*<range>
|j4 = [0, 0, 0]= 0 1 2 3 ... 6 7 8 9 (len=10)
[0, 0, 1]= 10 11 12 13 ... 16 17 18 19 (len=10)
...
[0, 0, 8]= 80 81 82 83 ... 86 87 88 89 (len=10)
[0, 0, 9]= 90 91 92 93 ... 96 97 98 99 (len=10)
[0, 1, 0]= 100 101 102 103 ... 106 107 108 109 (len=10)
[0, 1, 1]= 110 111 112 113 ... 116 117 118 119 (len=10)
...
[0, 1, 8]= 180 181 182 183 ... 186 187 188 189 (len=10)
[0, 1, 9]= 190 191 192 193 ... 196 197 198 199 (len=10)
...
[0, 8, 0]= 800 801 802 803 ... 806 807 808 809 (len=10)
[0, 8, 1]= 810 811 812 813 ... 816 817 818 819 (len=10)
...
[0, 8, 8]= 880 881 882 883 ... 886 887 888 889 (len=10)
[0, 8, 9]= 890 891 892 893 ... 896 897 898 899 (len=10)
[0, 9, 0]= 900 901 902 903 ... 906 907 908 909 (len=10)
[0, 9, 1]= 910 911 912 913 ... 916 917 918 919 (len=10)
...
[0, 9, 8]= 980 981 982 983 ... 986 987 988 989 (len=10)
[0, 9, 9]= 990 991 992 993 ... 996 997 998 999 (len=10)
[1, 0, 0]= 1000 1001 1002 1003 ... 1006 1007 1008 1009 (len=10)
[1, 0, 1]= 1010 1011 1012 1013 ... 1016 1017 1018 1019 (len=10)
...
**(略します)**
...
[9, 9, 8]= 9980 9981 9982 9983 ... 9986 9987 9988 9989 (len=10)
[9, 9, 9]= 9990 9991 9992 9993 ... 9996 9997 9998 9999 (len=10)
<list>:10*<numpy.ndarray>:10*<numpy.ndarray>:10*<numpy.ndarray>:10*<numpy.int32>
|j5 = [0, 0, 0]= 0 1 2 3 ... 6 7 8 9 (len=10)
[0, 0, 1]= 10 11 12 13 ... 16 17 18 19 (len=10)
...
**(略します)**
[9, 8, 9]= 90 91 92 93 ... 96 97 98 99 (len=10)
[9, 9, 0]= 0 1 2 3 ... 6 7 8 9 (len=10)
[9, 9, 1]= 10 11 12 13 ... 16 17 18 19 (len=10)
...
[9, 9, 8]= 80 81 82 83 ... 86 87 88 89 (len=10)
[9, 9, 9]= 90 91 92 93 ... 96 97 98 99 (len=10)
<list>:10*<list>:10*<list>:10*<numpy.ndarray>:10*<numpy.int32>
|k1 = []
<tuple>:0
|k2 = []= 1 2 3 4 ... 7 8 9 0 (len=10)
<tuple>:10*<int>
|k3 = 1 2 3
1 2 3
...
1 2 3
1 2 3
<tuple>:90*<tuple>:3*<int>
|l = {'one': 1, 'two': 2, 'three': 3}
<dict>
|{'key':100} = {'key': 100}
<dict>
お出かけ先で「夜ごと太るボクのために」

もうすこしダイエットをこころがけます。