python class
とある本を読んでいたら面白い紹介が出てた。
これ、以前NHKのバクモンで、ぐぐるJapanへの潜入番組をやってた時、ちらっと移ったその筋の人達の仕業だな。 モールス符号で、日本語入力させるなんて、JARLから表彰状を送っても罰が当たらないぞ。 そのうちに、世界文化遺産に登録されたりして。
そして、ハッカー集団のぐぐる連中は、イースターエッグを仕込んで憂さ晴らしをしてるぞ。
zerg rush とか askew とか blink html とか do a barrel roll で、検索すると、 思わず、にやりとなる事間違いなし。
ゲームも用意されてるぞ。
https://macek.github.io/google_pacman/
ruby to python
前回出てきたrubyでforthを書くやつ。練習がてらpythonに移植しよかと思った。その前に rubyで走らせてみよう。得意のコピペをして走らせると、
[cent tmp]$ ruby a.rb a.rb:6: syntax error, unexpected tSTRING_BEG, expecting keyword_do or '{' or '(' '.s' => lambda { p @stack }, : a.rb:33: syntax error, unexpected end-of-input, expecting keyword_end
ずらーと、こんなエラーが出てきた。コピペの祟りかなあ。どうもコピペした時に、余計な 文字が紛れこんでいる予感。
こういう時は、hexdump -C とかやって、調べるのが定番。でも、このコマンドちょいと長すぎ ないか。もっと短めって事で相当品を調べたら、odってのに思いが至った。hexdumpと同様な 事をやろうとすると、ごちゃごちゃオプションを付けないといけない。こういう時はエイリアス。
alias hd='od -A x -t x1z -v'
で、確認すると、
[cent tmp]$ hd a.rb 000000 63 6c 61 73 73 20 4d 79 46 6f 72 74 68 0a c2 a0 >class MyForth...< 000010 0a c2 a0 c2 a0 64 65 66 20 69 6e 69 74 69 61 6c >.....def initial< 000020 69 7a 65 0a c2 a0 c2 a0 c2 a0 c2 a0 40 73 74 61 >ize.........@sta< 000030 63 6b 20 3d 20 5b 5d 0a c2 a0 c2 a0 c2 a0 c2 a0 >ck = [].........< 000040 40 77 6f 72 64 73 20 3d 20 7b 0a c2 a0 c2 a0 c2 >@words = {......< 000050 a0 c2 a0 c2 a0 c2 a0 27 2e 73 27 20 3d 3e 20 6c >.......'.s' => l< 000060 61 6d 62 64 61 20 7b 20 70 20 40 73 74 61 63 6b >ambda { p @stack< :
c2a0なんていう不吉なバイトで、スペースの代わりをさせている事が判明した。たまに、 コードを画像にすると言う意地悪に出会うけど、これも新種の意地悪か。
[cent tmp]$ cat a.rb | sed -e 's/\xc2\xa0/ /g' > b.rb
sedを知ってて良かったよ。あえていうほどでもないsed入門
[cent tmp]$ ruby b.rb 1 2 3 4 5 + .s [1, 2, 3, 9]
思った通り普通に動く。さて、変換するぞ。
import re class MyForth(): def __init__(self): self.stack = [] self.words = { '.s' : self.show, '+' : self.add } def show(self): print(self.stack) def add(self): self.stack.append(self.stack.pop() + self.stack.pop()) def eval_word(self, word): pat = r'^-?\d+$' m = re.match(pat, word) if m: self.stack.append(int(word)) elif self.words[word]: self.words[word]() else: print("ERROR: Unsupported word") def main(): forth = MyForth() while (True): line = input('forth> ') for word in line.split(): forth.eval_word(word) if __name__ == '__main__': main()
面倒なので、足し算しか出来ません。だって、pythonのlambda式ってとっても貧弱なんだもの。何かの記事で読んだんだけど、複雑な事はちゃんとした名前を付けて、分かり易いようにしろ って事で、式が一つしか書けないようにしてるとか。
lambda arg1,arg2 .. : exp same as def <lambda>(arg1,arg2 ..): return exp
という風に、解釈されるとな。lisp屋さんからみたら、なんじゃいこれって代物ですよ。 まあ、こういう制限を課して、pythonになってるんだなあ。
どうしてもlambdaを使ってみようとすると、その制限から、使えない場合が有り、下記の ように不格好な事になる訳です。
def __init__(self): self.stack = [] self.words = { '.s' : lambda : print(self.stack), '+' : lambda : self.stack.append(self.stack.pop() + self.stack.pop()), '-' : self.sub, '*' : lambda : self.stack.append(self.stack.pop() * self.stack.pop()), '/' : self.div, } def sub(self): x,y = self.stack.pop(), self.stack.pop() self.stack.append(y - x) def div(self): x,y = self.stack.pop(), self.stack.pop() self.stack.append(int(y / x))
それにしても、いちいちselfって書かされるのも不格好の極みと思うぞ。classはオイラー的に 封印したい所存ですよ。
>>> a = [1, 10, 3] >>> a.append(a.pop(-2) - a.pop()) >>> a [1, 7]
今思いついたんだけど、上のsubとかで、演算に使うデータを2個分取り出してきて、それを ひっくり返して使ってる。pop(-2)のようにすれば、stackTopから2番目のデータを得られるので、無駄な事をしなくて済むね。
あれ? 引数の処理と言うか評価は左側からって仮定を 置いてしまったけど、文書化されてるのかしら? lisperと言うかschemeをやってた人は、 つい余計な心配をしてしまうのでした。
ちゃんとしたforthを作っておられる方が居ました。面白いので、追いかけてみるかな。
Python VMのコードを見てたら、forthのコードが出てきた。演算データを右と左で 受けるって、中置記法をイメージしてるんだな。
PyDECREFは、使ったオブジェクトのカウンターを減らすマクロ。 SET_TOPは、stackTopに値を置けってマクロ。 DISPATCHは、次のVMコードへ行けってマクロだ。
TARGET(BINARY_SUBTRACT) { PyObject *right = POP(); PyObject *left = TOP(); PyObject *diff = PyNumber_Subtract(left, right); => Py_DECREF(right); Py_DECREF(left); SET_TOP(diff); if (diff == NULL) goto error; DISPATCH(); }
Rnd
さて、そろそろ自前の乱数クラスを作っておくか。目標は、今まで何度も出てるパスワード 発生器を動かす事。
パイソンバッテリーに、ランダムクラスが有るので、それを継承するか。そんな面倒な事を しなくても、コードをコピペ移植すればおk。
クラス名何にしよう? Rnd ぐらいでいいだろう。Randomの省略形。東京ディズニーランドをランドと通な人は 良く言うからね。それに倣ってみました。
### rnd from hashlib import sha256 as _sha256 from string import ascii_lowercase, ascii_uppercase, digits class Rnd: def seed(self, a): a = a.encode() a += _sha256(a).digest() a = int.from_bytes(a, 'big') self.tane = a def random(self, n): rv = self.tane % n self.tane //= n return rv def shuffle(self, x): for i in reversed(range(1, len(x))): j = self.random(i+1) x[i], x[j] = x[j], x[i] def sample(self, population, k): n = len(population) result = [None] * k pool = list(population) for i in range(k): j = self.random(n-i) result[i] = pool[j] pool[j] = pool[n-i-1] return result def pg(self, se): self.seed(se) sel = self.sample(digits, 4) + \ self.sample('@.,_#%+=;:~', 4) + \ self.sample(ascii_uppercase, 4) + \ self.sample(ascii_lowercase, 4) self.shuffle(sel) print(''.join(sel[:10])) _inst = Rnd() tane = _inst.tane random = _inst.random seed = _inst.seed pg = _inst.pg if __name__ == '__main__': s = input('Enter secret> ') pg(s)
自分でコードを書いてみると、知らない事が出てくるな。整数の割り算は、'//' とか、 こっそりインスタンスを作っておくとか。やっぱり体験してナンボの世界だな。コピペは 自戒して写経しろってのもうなずけるよ。(//を直接使うより、divmodを使う方がスマート かな)
[debian tmp]$ python rnd.py Enter secret> 123 M=,bk+95NX
実行してみた。
[debian tmp]$ python Python 3.6.0 |Continuum Analytics, Inc.| (default, Dec 23 2016, 12:22:00) [GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import rnd >>> rnd.pg('123') M=,bk+95NX >>> rnd.tane() 3472236684284114745068887947519295675475893359413423 >>> rnd.seed('123') >>> rnd.tane() 373327087054888413006251554247663835968019142665876459085583381368111691756604652259
インポートして、いきなり使ってみた。いちいちインスタンスを作らなくても使える嬉しい 仕様。
計算後、乱数の種がどれだけ残ってるか、確認。結構残ってるね。こういうの何桁有るかは、 ちょいとした裏技で、フロートにしてみると、直ぐに分かる。
>>> float(rnd.tane()) 3.7332708705488844e+83
で、まだ十分にエントロピーが残ってて、もったいない。有効に使う算段をしよう。 一番に思いつくのは、サンプリングする前に、シャッフルする方法。
def pg(self, se): self.seed(se) ma = self.shuffle sel = self.sample(ma(digits), 4) + \ :
[debian tmp]$ python rnd.py Enter secret> hoge Traceback (most recent call last): File "rnd.py", line 62, in <module> pg(s) File "rnd.py", line 43, in pg self.sample(ascii_uppercase, 4) + \ File "rnd.py", line 26, in shuffle x[i], x[j] = x[j], x[i] TypeError: 'str' object does not support item assignment
ちょっとエラーを起こした行がおかしいと思うけど、shuffleは、schemeで言う、shuttle! 相当(引数を直接書き換え)してるんで、そんな事は許しませんよ、と言う事だな。 きっと、巨大なリストをコピーするのが嫌だったんでしょう。
値を返す関数と、引数を変更する関数の区別がパッと見、区別出来ない。これ、Pythonの 大きな欠点だと思うぞ。引数を変更する関数にはビックリマークを付けてくれ。ああ、記号は 関数名の一部には出来ないと言う不自由な制限が有るか。ならば譲歩して、関数名の最後にMと か付けるのはどうだ。丁度、lispに取り入れている、numberPみたいにするんだ。 どうでっしゃろな? PEPって誰でも提案出来るのかな?
それなら、pgの最後にあるshuffleを、エントロピーの許す限り繰り返すという事で、お茶を 濁す事にします。 3回ぐらい追加のシャッフルをして、丁度よい塩梅になりましたよ。
今度は、名前空間に無いsampleを試してみます。
>>> import rnd >>> dir(rnd) ['Rnd', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_inst', '_sha256', 'ascii_lowercase', 'ascii_uppercase', 'digits', 'pg', 'random', 'seed', 'tane'] >>> rnd.sample('1234567890', 4) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: module 'rnd' has no attribute 'sample'
そんなの知らんと怒られた。当然の事だな。使うなら ちゃんとインスタンスを作ってからね。
>>> z = rnd.Rnd() >>> z.seed('') >>> z.sample('123456789', 4) ['8', '7', '6', '3']
面倒くさいけど、安全策。
クラス名と、ファイル名を一致させとけと言われているけど、この禁を破ったらどうなる? 良い機会なので、実験しとく。
[debian tmp]$ python hoge.py Enter secret> 123 =Md4m+9Nb3 [debian tmp]$ python -q >>> import hoge >>> hoge.pg('123') =Md4m+9Nb3 >>> rnd.pg('123') Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'rnd' is not defined
紛らわしいから、一致させとけが良案。名(ファイル名)は、体(クラス名)を表すってのは、 脳内負荷低減に寄与します。
後、コードを見てたら、__all__ なんてのが出てきてたので、なんじゃらほいと言う事で、
です。