レイトレーシング 2018春 の えらいこっちゃ で、自宅のpython環境のデフォルトがpython2からpython3に上がってしまいました。
これをきっかけに、これまで書いたpython2のソースのいくつかを python3 対応してみたので、自分用のメモです。 (自分のコードの癖に合わせた対策メモです)
ぱっと思い出す大きなのは
他
引数のアンパック廃止は、とにかくインパクトが大きかったです。
dict( map( lambda (k, v): (xxx, yyy), dic.itmes() ) )
とか多用してましたので。
for文のアンパックは安泰なので
d = {} for (k, v) in dic.items(): d[xxx] = yyy
とか
dict( [ (xxx, yyy) for (k, v) in dic.items() ] )
は無事なのが悔しいところ。
とにかくmap()を多用してたので、 えらいこっちゃ の v15.patch では、ユーテイリティ用の ut.py に真っ先に
: +map_lst = lambda f, lst: list( map( f, lst ) ) +map_up = lambda f, lst: map( lambda a: f(*a), lst ) +map_up_lst = lambda f, lst: list( map_up( f, lst ) ) + +filter_lst = lambda f, lst: list( filter( f, lst ) ) +filter_up = lambda f, lst: filter( lambda a: f(*a), lst ) +filter_up_lst = lambda f, lst: list( filter_up(f, lst) ) + :
を用意してました。
_up は「アンパック」です。
例えば先の
dict( map( lambda (k, v): (xxx, yyy), dic.itmes() ) )
は
dict( ut.map_up( lambda k, v: (xxx, yyy), dic.itmes() ) )
に書き換えてまわりました。
よくよく見ればそのままのコードで python3 の恩恵にあずかれるパターンもあったりします。
とにかく python3 でも動けばよしとするならば、目をつむって
map(...) --> ut.map_lst(...) ut.map_up(...) --> ut.map_up_lst(...)
に書き換えです。
機械的に書き換え作業してると、なかなか気付かずにハマった箇所がありました。
パックマンもどき2017秋 の クロージャと空クラス で書いた「継承もどき」
def cls_to_dic(c): ks = filter( lambda k: not k.startswith('_'), dir(c) ) return dict( map( lambda k: ( k, getattr(c, k) ), ks ) ) クラスのアトリビューとで '_' で始まるもの以外を、辞書に。 def dic_to_cls(d, c=None): if not c: c = Empty() map( lambda (k, v): setattr(c, k, v), d.items() ) return c 辞書を受け取って、クラスのアトリビュートに上書き。 def extends(pcls, c=None): if not c: c = Empty() dic_to_cls( cls_to_dic(pcls), c ) return c
cls_to_dic()側のmapは結果のイテレータをdict()で回すので問題なくて、 問題はdic_to_cls()側のmap。
ここでは lambda式の返す値はどうでもよくて、setattr()の副作用というか、 クラスcのアトリビュートを設定する事だけを目的にしてます。
なまじ lambda で引数のアンパックを使っているので、 そちらに目がいってしまって
map( lambda (k, v): setattr(c, k, v), d.items() )
を
ut.map_up( lambda k, v: setattr(c, k, v), d.items() )
に書き換えてみて、さて動かない... アトリビュートが設定されてない。なんで?
map()はイテレータを返すだけです。 list()をとるなどして消費しないと回りませんでした。
まぁ、結局対応は
ut.map_up_lst( lambda k, v: setattr(c, k, v), d.items() )
でした。
これは、新規にreduceを使うモチベーションが下がってしまいました。
既に使ってるところは、 v15.patch
: --- rt_v14/lstx.py 2018-03-31 17:25:01.000000000 +0900 +++ rt_v15/lstx.py 2018-04-02 18:13:19.000000000 +0900 @@ -1,5 +1,6 @@ #!/usr/bin/env python +from functools import reduce import ut import v import line
などと追加です。
python2 でstr(文字列)が返ってたいくつかの箇所が、 python3 でbytesが返るようになり、怒られてたはずです。
例えば v24.patch
def imgs_to_video(img_name, video_name, fps, zm): cmd = 'ls {}[0-9]*.jpg'.format(img_name) - lst = ut.cmd_exec(cmd).strip().split('\n') + lst = ut.cmd_exec(cmd).decode().strip().split('\n')
では、decode()でstrに戻す対応を入れました。
ut.py : def cmd_exec(cmd): try: return subprocess.check_output(cmd, shell=True) except: return '' :
subprocess の実行結果の文字列は python3 で str から bytes に変更
python3 の環境では
>>> s = ' foo.bar ' >>> type(s) <class 'str'> >>> b = s.encode() >>> type(b) <class 'bytes'> >>> s2 = b.decode() >>> type(s2) <class 'str'> >>> s2 ' foo.bar ' >>> len(s) 10 >>> len(b) 10
len()は問題なし
>>> s.strip() 'foo.bar' >>> b.strip() b'foo.bar'
strip()も使える
>>> s.strip().split('.') ['foo', 'bar'] >>> b.strip().split('.') Traceback (most recent call last): File "<stdin>", line 1, inTypeError: 'str' does not support the buffer interface
bytes の split(xxx) は引数がbytesでないとダメか
>>> b.strip().split( '.'.encode() ) [b'foo', b'bar']
socket の sendall() の引数や recv() の返り値も、同様です。 pthon3 から bytes です。
python2 では、バイナリファイルを open(fn, 'r') して read() すれば、strとして読めました。
例えば
$ which cat /bin/cat $ head /bin/cat > /tmp/f.bin $ hd /tmp/f.bin | head 00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000010 02 00 03 00 01 00 00 00 c2 9e 04 08 34 00 00 00 |............4...| 00000020 c4 b2 00 00 00 00 00 00 34 00 20 00 09 00 28 00 |........4. ...(.| 00000030 1c 00 1b 00 06 00 00 00 34 00 00 00 34 80 04 08 |........4...4...| 00000040 34 80 04 08 20 01 00 00 20 01 00 00 05 00 00 00 |4... ... .......| 00000050 04 00 00 00 03 00 00 00 54 01 00 00 54 81 04 08 |........T...T...| 00000060 54 81 04 08 13 00 00 00 13 00 00 00 04 00 00 00 |T...............| 00000070 01 00 00 00 01 00 00 00 00 00 00 00 00 80 04 08 |................| 00000080 00 80 04 08 d8 a4 00 00 d8 a4 00 00 05 00 00 00 |................| 00000090 00 10 00 00 01 00 00 00 08 af 00 00 08 3f 05 08 |.............?..| $ python2 >>> f = open( '/tmp/f.bin', 'r' ) >>> s = f.read() >>> type(s)>>> len(s) 6781 >>> s8 = s[:8] >>> s8 '\x7fELF\x01\x01\x01\x00'
python3 では
$ python3 >>> f = open( '/tmp/f.bin', 'r' ) >>> s = f.read() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python3.4/codecs.py", line 319, in decode (result, consumed) = self._buffer_decode(data, self.errors, final) UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 61: invalid start byte read でエラー >>> fb = open( '/tmp/f.bin', 'rb' ) 'rb' で open すれば大丈夫で >>> b = fb.read() >>> type(b) <class 'bytes'> bytes で読めます >>> b8 = b[:8] >>> b8 b'\x7fELF\x01\x01\x01\x00'
「逆もまた真なり」か、どうかは別として、バイナリファイルを作るときも同様です
'wb' で open して bytes を write() します。
パックマンもどき2017秋 の v7.patch で python3 対応をしました。
os.read() も str から bytes に変わってたので、 ここでは、キー操作の処理でひっかかりました。
import sys import os : fd = sys.stdin.fileno() s = os.read(fd, 1)
などと、sys.stdin の fd から 1文字読み出す箇所で、 strからbytes に変わってしまうので、 対応としては、os.read()結果をdecode()してbytesからstrにしてました。
また、python2 でバイナリデータをリダイレクトで stdin に与えて、 sys.stdin.read() していた場合も要注意です。
python3 で、stdin からバイナリをリードするには、 sys.stdin でなく sys.stdin.buffer を使います。
同様に、sys.stdout にバイナリを出力するには、 sys.stdout でなく sys.stdout.buffer を使えば良いはず。
import sys import six def read_bin(fn): f = ( sys.stdin.buffer if six.PY3 else sys.stdin ) if fn == '-' else open(fn, 'rb') bs = f.read() if fn != '-': f.close() return bs def write_bin(fn, bs): f = ( sys.stdout.buffer if six.PY3 else sys.stdout ) if fn == '-' else open(fn, 'wb') f.write(bs) if fn != '-': f.close()
$ python2 >>> 1/3 0 $ python3 >>> 1/3 0.3333333333333333 結果がfloatに! 整数の割算は '//' との事 >>> 1//3 0
なのですが、'//' って C++のコメントのキーワードって印象が強くてどうも...
なので
$ python3 >>> int(1/3) 0
な対応をしてまわりました。python2 への後方互換もあるので。
しかしpython3のこの仕様は、これまでpython2で整数同士の割算結果が、 floatにならなくて困ってた人々が多くいたって事だろうか...?
整数のレジスタどうしで割算したら、結果の入る整数のレジスタには、整数しか返らんと考えますが... 考えが古いのでしょうか。
関数になったので '(' ... ')' で囲みます。
ということは、タプルにして表示したかったところが、ちょっとアッサリ風味になりますね。
$ python2 >>> print(1,2) (1, 2) >>> print (1,2) (1, 2) $ python3 >>> print(1,2) 1 2 >>> print (1,2) 1 2 になってしまうので >>> print((1,2)) (1, 2) >>> print ((1,2)) (1, 2)