python class

とある本を読んでいたら面白い紹介が出てた。

dram

morse

patapata

これ、以前NHKのバクモンで、ぐぐるJapanへの潜入番組をやってた時、ちらっと移ったその筋の人達の仕業だな。 モールス符号で、日本語入力させるなんて、JARLから表彰状を送っても罰が当たらないぞ。 そのうちに、世界文化遺産に登録されたりして。

Google 日本語入力チームからの新しいご提案

そして、ハッカー集団のぐぐる連中は、イースターエッグを仕込んで憂さ晴らしをしてるぞ。

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)

ちゃんとした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__ なんてのが出てきてたので、なんじゃらほいと言う事で、

Python __all__について

です。