第 4 回 移植に使えるユーティリティ その2

今回は、「第 3 回 移植に使えるユーティリティ その1」で紹介した移植ユーティリティについての変換例を紹介します。

削除されたメソッドのケース (python-modernize 使用の場合)

$ cat samp_remove.py
import itertools

d1 = {'a': '1', 'b': '2'}

b = b'abcd'
u = unicode(b)
if u == b:
    print "equal binary u=%s" % u
else:
    print "not binary u=%s" % u

for k, v in d1.iteritems():
    print "iteritems() k=%s v=%s" % (k, v)

ivals = d1.itervalues()
while True:
    n = ivals.next()
    print "iterator.next() n=%s" % n
    if n == '2':
        break

for i in xrange(2):
    print "xrange() i=%d" % i

it = itertools.imap(str, [1, 2])

for s in it:
    print "itertools.imap() s=%s" % s

a = 1
b = 2
if cmp(a, b) < 0:
    print "a < b"
$ python-modernize -w samp_remove.py
root: Generating grammar tables from /usr/lib/python2.7/lib2to3/PatternGrammar.txt
…
RefactoringTool: Refactored samp_remove.py
--- samp_remove.py      (original)
+++ samp_remove.py      (refactored)
@@ -1,32 +1,36 @@
+from __future__ import absolute_import
+from __future__ import print_function		-- 1)
 import itertools
+import six
+from six.moves import range

 d1 = {'a': '1', 'b': '2'}

 b = b'abcd'
-u = unicode(b)
+u = six.text_type(b) 				-- 2)
 if u == b:
-    print "equal binary u=%s" % u
+    print("equal binary u=%s" % u) 			-- 3)
 else:

-    print "not binary u=%s" % u
+    print("not binary u=%s" % u)

-for k, v in d1.iteritems():
-    print "iteritems() k=%s v=%s" % (k, v)
+for k, v in six.iteritems(d1):			-- 4)
+    print("iteritems() k=%s v=%s" % (k, v))

-ivals = d1.itervalues()
+ivals = six.itervalues(d1)
 while True:
-    n = ivals.next()
-    print "iterator.next() n=%s" % n
+    n = next(ivals)
+    print("iterator.next() n=%s" % n)
     if n == '2':
         break

-for i in xrange(2):
-    print "xrange() i=%d" % i
+for i in range(2):
+    print("xrange() i=%d" % i)

 it = itertools.imap(str, [1, 2]) 			-- 5)
 for s in it:
-    print "itertools.imap() s=%s" % s
+    print("itertools.imap() s=%s" % s)

 a = 1

b = 2
 if cmp(a, b) < 0:				-- 5)
-    print "a < b"
+    print("a < b")
RefactoringTool: Files that were modified:
RefactoringTool: samp_remove.py 
$
編集
$ diff -u samp_remove.py.bak samp_remove.py
--- samp_remove.py.bak  2015-09-03 06:29:19.142521670 +0000
+++ samp_remove.py      2015-09-03 06:29:51.166738014 +0000
@@ -26,11 +26,11 @@
 for i in range(2):
     print("xrange() i=%d" % i)

-it = itertools.imap(str, [1, 2]) 			-- 6)
+it = map(str, [1, 2])
 for s in it:
     print("itertools.imap() s=%s" % s)

 a = 1
 b = 2
-if cmp(a, b) < 0:				-- 6)
+if a < b:
     print("a < b")
$
  1. Python 2 で print() 関数をサポートするために future 文を追加
  2. Python 2 では unicode()、Python 3 では str() に展開
  3. print 文 を print 関数に変換
  4. Python 2 では iter(dict.iteritems())、Python 3 では iter(dict.items()) に展開
    dict_items オブジェクトはイテレータとは属性が異なるためイテレータに変換
  5. 変換が必要だが未変換
  6. 変換が必要だがユーティリティでは対象外になっているため、手動で変更

【実行結果】
Python 2 と Python3 で動作確認

変換前

$ python2 samp_remove.py
equal binary u=abcd
iteritems() k=a v=1
iteritems() k=b v=2
iterator.next() n=1
iterator.next() n=2
xrange() i=0
xrange() i=1
itertools.imap() s=1
itertools.imap() s=2
a < b
$ python3 samp_remove.py
  File "samp_remove.py", line 6
    print "equal binary u=%s" % u
                            ^
SyntaxError: invalid syntax
$

変換後

$ python2 samp_remove.py
equal binary u=abcd
iteritems() k=a v=1
iteritems() k=b v=2
iterator.next() n=1
iterator.next() n=2
xrange() i=0
xrange() i=1
itertools.imap() s=1
itertools.imap() s=2
a < b
$ python3 samp_remove.py
not binary u=b'abcd'
iteritems() k=a v=1
iteritems() k=b v=2
iterator.next() n=1
iterator.next() n=2
xrange() i=0
xrange() i=1
itertools.imap() s=1
itertools.imap() s=2
a < b
$

削除されたメソッドのケース (2to3 使用の場合)

$ 2to3 -w samp_remove.py
root: Generating grammar tables from /usr/lib/python2.7/lib2to3/PatternGrammar.txt
…
RefactoringTool: Refactored samp_remove.py
--- samp_remove.py      (original)
+++ samp_remove.py      (refactored)
@@ -3,30 +3,30 @@
 d1 = {'a': '1', 'b': '2'}

 b = b'abcd'
-u = unicode(b)
+u = str(b) 					-- 1)
 if u == b:
-    print "equal binary u=%s" % u
+    print("equal binary u=%s" % u)
 else:
-    print "not binary u=%s" % u
+    print("not binary u=%s" % u)

-for k, v in d1.iteritems():
-    print "iteritems() k=%s v=%s" % (k, v)
+for k, v in d1.items():				-- 2)
+    print("iteritems() k=%s v=%s" % (k, v))

-ivals = d1.itervalues() 				-- 3)
+ivals = iter(d1.values())
while True:
-    n = ivals.next()
-    print "iterator.next() n=%s" % n
+    n = next(ivals)
+    print("iterator.next() n=%s" % n)
     if n == '2':
         break

-for i in xrange(2):
-    print "xrange() i=%d" % i
+for i in range(2):
+    print("xrange() i=%d" % i)

-it = itertools.imap(str, [1, 2])
+it = map(str, [1, 2]) 				-- 4)
 for s in it:
-    print "itertools.imap() s=%s" % s
+    print("itertools.imap() s=%s" % s)

 a = 1
 b = 2
 if cmp(a, b) < 0:
-    print "a < b"
+    print("a < b")
$
  1. str() に変換
  2. dict.items() に変換
  3. iter(d1.values()) に変換
  4. map() に変換 (python-modernize() では変換されていなかった)

戻り値がリストからイテレータに変わったケース

$ cat samp_iterator.py
d1 = {'a': '1', 'b': '2', 'c': '3'}
for k, v in d1.items():
    print "items() k=%s" % k

s = map(str, [1, 2, 3])
print "map=%s" % s

s = map(str, [1, 2, 3])[1]
print "map=%s" % s
$ python-modernize -w samp_iterator.py
root: Generating grammar tables from /usr/lib/python2.7/lib2to3/PatternGrammar.txt
…
RefactoringTool: Refactored samp_iterator.py
--- samp_iterator.py    (original)
+++ samp_iterator.py    (refactored)
@@ -1,9 +1,12 @@
+from __future__ import absolute_import
+from __future__ import print_function
+from six.moves import map
 d1 = {'a': '1', 'b': '2', 'c': '3'}
-for k, v in d1.items():
-    print "items() k=%s" % k
+for k, v in list(d1.items()):
+    print("items() k=%s" % k)

-s = map(str, [1, 2, 3])
-print "map=%s" % s
+s = list(map(str, [1, 2, 3]))
+print("map=%s" % s)

 s = map(str, [1, 2, 3])[1]
-print "map=%s" % s
+print("map=%s" % s)
RefactoringTool: Files that were modified:
RefactoringTool: samp_iterator.py
$
編集
$ diff -u samp_iterator.py.bak samp_iterator.py
--- samp_iterator.py.bak        2015-09-02 10:36:48.880141958 +0000
+++ samp_iterator.py    2015-09-02 10:43:56.027294096 +0000
@@ -1,9 +1,12 @@
+from __future__ import absolute_import
+from __future__ import print_function
+from six.moves import map
 d1 = {'a': '1', 'b': '2', 'c': '3'}
-for k, v in d1.items():
-    print "items() k=%s" % k
+for k, v in list(d1.items()):			-- 1)
+    print("items() k=%s" % k)

-s = map(str, [1, 2, 3])
-print "map=%s" % s
+s = list(map(str, [1, 2, 3]))
+print("map=%s" % s)
-s = map(str, [1, 2, 3])[1]
-print "map=%s" % s
+s = list(map(str, [1, 2, 3]))[1]
+print("map=%s" % s)
$ python2 samp_iterator.py
items() k=a
items() k=c
items() k=b
map=['1', '2', '3']
map=2
$ python3 samp_iterator.py
items() k=b
items() k=c
items() k=a
map=['1', '2', '3']
map=2
$
  1. ここではイテレータのままでも問題ないが、ユーティリティが無条件に list() を使用する様に変換している。

str 型と bytes 型

$ cat samp_str.py
import struct

p = struct.pack('2sisi', 'aa', 3, 'b', 100)
print "struct.pack()=", struct.unpack('2sisi', p)

s = b'abcde'
b_cmp = 'c'
if s[2] == b_cmp:
    print "s[2] equal %s:" % b_cmp, s[2]
else:
    print "s[2] not equal %s:" % b_cmp, s[2]
$ python-modernize -w samp_str.py
…
$
編集
$ diff -u samp_str.py.bak samp_str.py
--- samp_str.py.bak     2015-09-03 23:48:54.612342691 +0000
+++ samp_str.py 2015-09-03 23:51:39.613462446 +0000
@@ -1,11 +1,13 @@
+from __future__ import absolute_import
+from __future__ import print_function
 import struct

-p = struct.pack('2sisi', 'aa', 3, 'b', 100)
-print "struct.pack()=", struct.unpack('2sisi', p)
+p = struct.pack('2sisi', b'aa', 3, b'b', 100) 		-- 1)
+print("struct.pack()=", struct.unpack('2sisi', p))

 s = b'abcde'
-b_cmp = 'c'
+b_cmp = 99				- - 2)
 if s[2] == b_cmp:
-    print "s[2] equal %s:" % b_cmp, s[2]
+    print("s[2] equal %s:" % b_cmp, s[2])
 else:
-    print "s[2] not equal %s:" % b_cmp, s[2]
+    print("s[2] not equal %s:" % b_cmp, s[2])
$ python2 samp_str.py
struct.pack()= ('aa', 3, 'b', 100)
s[2] not equal 99: c
$ python3 samp_str.py
struct.pack()= (b'aa', 3, b'b', 100)
s[2] equal 99: 99
$
  1. 書式 s の引数を bytes 型に変更
  2. Python 3 では bytes のインデックスでの参照は数字になるため変更
編集
$ diff -u samp_str.py.bak samp_str.py
--- samp_str.py.bak3    2015-09-03 23:51:39.613462446 +0000
+++ samp_str.py 2015-09-03 23:53:17.390125938 +0000
@@ -1,12 +1,16 @@
 from __future__ import absolute_import
 from __future__ import print_function
+import six
 import struct

 p = struct.pack('2sisi', b'aa', 3, b'b', 100)
 print("struct.pack()=", struct.unpack('2sisi', p))

 s = b'abcde'
-b_cmp = 99
+if six.PY3:
+    b_cmp = 99
+else:
+    b_cmp = 'c'
 if s[2] == b_cmp:
     print("s[2] equal %s:" % b_cmp, s[2])
 else:
$ python2 samp_str.py
struct.pack()= ('aa', 3, 'b', 100)
s[2] equal c: c
$ python3 samp_str.py
struct.pack()= (b'aa', 3, b'b', 100)
s[2] equal 99: 99
$

contextlib.nested

$ cat samp_nested.py
import contextlib
import mock

with contextlib.nested(
    mock.patch.object(self.ovs, 'set_protocols'),
    mock.patch.object(self.ovs, 'set_controller'),
) as (mock_set_protocols, mock_set_controller):
    self.assertEqual(mock_set_protocols.call_count, 1)
    self.assertEqual(mock_set_controller.call_count, 1)
$ cp -p samp_nested.py samp_nested.py.orig
$ context_unnester/context_unnester.py samp_nested.py
samp_nested.py
$ context_unnester.py samp_nested.py
samp_nested.py
$ cat samp_nested.py
import mock

with mock.patch.object(self.ovs, 'set_protocols') as mock_set_protocols,
        mock.patch.object(self.ovs, 'set_controller') as mock_set_controller:
    self.assertEqual(mock_set_protocols.call_count, 1)
    self.assertEqual(mock_set_controller.call_count, 1)
$
$ cat samp_misc.py
import sys
i = 5 / 2
print >> sys.stderr, "i=",
print >> sys.stderr, i
i = 5 // 2
print >> sys.stderr, "i=", i

try:
    raise IOError, 'some errors occured'
except IOError, ex:
    print "ex=%s" % ex.message
$ python-modernize -w samp_misc.py
root: Generating grammar tables from /usr/lib/python2.7/lib2to3/PatternGrammar.txt
…
$
編集
$ diff -u samp_misc.py.bak samp_misc.py
--- samp_misc.py.bak    2015-09-02 04:25:08.438598822 +0000
+++ samp_misc.py        2015-09-02 04:27:31.175616793 +0000
@@ -1,11 +1,13 @@
+from __future__ import absolute_import
+from __future__ import print_function
 import sys
 i = 5 / 2
-print >> sys.stderr, "i=",
-print >> sys.stderr, i				- 1)
+print("i=", end=' ', file=sys.stderr)
+print(i, file=sys.stderr)
 i = 5 // 2
-print >> sys.stderr, "i=", i
+print("i=", i, file=sys.stderr)

 try:
-    raise IOError, 'some errors occured'
-except IOError, ex:				-- 2)
-    print "ex=%s" % ex.message
+    raise IOError('some errors occured')
+except IOError as ex:
+    print("ex=%s" % ex.args[0])
$ python2 samp_misc.py
i= 2
i= 2
ex=some errors occurred
$ python3 samp_misc.py
i= 2.5					-- 3)
i= 2
ex=some errors occured
$
  1. 末尾の出力を end 引数に、出力先を file 引数に変換
  2. ex を as ex に。message 属性は削除されたので args に変換
  3. Python 3 では ‘/’ の結果は浮動小数点になる