【Python2to3アップデート方法】2to3でPython2系コードを3系用に自動変換

## 目次
- [【Python2to3アップデート方法】目次](https://qiita.com/Esfahan/items/067484ee0cb3dd374992)

## 概要
Python2系のコードを3系用に自動変換してくれるツールであり、Pythonに標準で同梱されているモジュールである**2to3**の使い方について説明する。

<font color="red"><b>ただし、このツールで変更が必要な箇所全てを網羅できるわけではないので注意してください。</b></font>

## この記事を読む前に
コード変換ツールはいくつかあり、それぞれ特徴が異なる。
まずは自分の環境にどのツールが合うのか、以下の記事を読んで判断することをオススメする。

- [【Python2to3アップデート方法】2to3 vs futurize どちらでPythonコード変換をすればよいのか](https://qiita.com/Esfahan/items/b9fd1e1ff2d599a651e7)

## 事前準備
まずは検証用に、2系では動くが3系では動かないコードを作成する。

### ディレクトリ構成
以下の構成で作成する。

```
.
`-- project
    |-- base.py
    `-- libs
        |-- __init__.py
        |-- base.py
        `-- sample_class.py
```

### 検証用コード
以下のファイルを作成する。

```py:project/base.py
# -*- coding:utf-8 -*-
import libs.sample_class as sc


if __name__ == '__main__':
    '''
    3系ではprint()にする必要がある
    '''
    print 'This is a sample.\n'
    sc.SampleClass().sample_method()
```

```py:project/libs/sample_class.py
# -*- coding:utf-8 -*-
'''
2系ではproject/libs/base.pyがimportされる
3系ではproject/base.pyがimportされる
'''
import base
'''
3系ではconfigparser
'''
import ConfigParser


class SampleClass(base.Base):

    def sample_method(self):
        '''
        3系ではprint()にする必要がある
        '''
        print '[division]'
        '''
        # 計算結果
        - 2系 => 0
        - 3系 => 0.5
        '''
        print '1/2 = {}'.format(1/2)
        print '1//2 = {}'.format(1//2)
```

```py:project/libs/base.py
# -*- coding:utf-8 -*-
from abc import ABCMeta, abstractmethod

class Base(object):
    '''
    3系ではAbstractの書き方が異なる
    '''
    __metaclass__ = ABCMeta

    @abstractmethod
    def sample_method(self):
        raise NotImplementedError()
```

project/libs/\_\_init\_\_.py
空で作成する。


### 実行結果
#### 2系での実行結果
以下が出力される。

```shell-session
$ python project/base.py
This is a sample.

[division]
1/2 = 0
1//2 = 0
```

#### 3系での実行結果
3系と互換性がない書き方なので、当然エラーとなる。

```shell-session
$ python project/base.py
Traceback (most recent call last):
  File "base.py", line 2, in <module>
    import libs.sample_class as sc
  File "/code/libs/sample_class.py", line 9
    print '[division]'
                     ^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print('[division]')?
```

## 2to3でコードを変換
### Installation
2to3を利用するためには以下のインストールが必要。

```bash
# CentOSの場合
$ yum install python-tools
# Ubuntuの場合
$ apt-get install 2to3
```

### Usage
#### 変更が必要な箇所をチェック
projectディレクトリに対して、2to3を実行すると、再帰的にチェックしてくれる。
この段階ではまだdry-runであり、コードの変換は行われない。

```diff
$ 2to3 project/
RefactoringTool: Skipping implicit fixer: buffer
RefactoringTool: Skipping implicit fixer: idioms
RefactoringTool: Skipping implicit fixer: set_literal
RefactoringTool: Skipping implicit fixer: ws_comma
RefactoringTool: Refactored project/base.py
--- project/base.py	(original)
+++ project/base.py	(refactored)
@@ -6,5 +6,5 @@
     '''
     3系ではprint()にする必要がある
     '''
-    print 'This is a sample.\n'
+    print('This is a sample.\n')
     sc.SampleClass().sample_method()
RefactoringTool: Refactored project/libs/base.py
--- project/libs/base.py	(original)
+++ project/libs/base.py	(refactored)
@@ -1,11 +1,10 @@
 # -*- coding:utf-8 -*-
 from abc import ABCMeta, abstractmethod

-class Base(object):
+class Base(object, metaclass=ABCMeta):
     '''
     3系ではAbstractの書き方が異なる
     '''
-    __metaclass__ = ABCMeta

     @abstractmethod
     def sample_method(self):
RefactoringTool: Refactored project/libs/sample_class.py
--- project/libs/sample_class.py	(original)
+++ project/libs/sample_class.py	(refactored)
@@ -4,11 +4,11 @@
 2系ではproject/libs/base.pyがimportされる
 3系ではproject/base.pyがimportされる
 '''
-import base
+from . import base
 '''
 3系ではconfigparser
 '''
-import ConfigParser
+import configparser


 class SampleClass(base.Base):
@@ -17,11 +17,11 @@
         '''
         3系ではprint()にする必要がある
         '''
-        print '[division]'
+        print('[division]')
         '''
         # 計算結果
         - 2系 => 0
         - 3系 => 0.5
         '''
-        print '1/2 = {}'.format(1/2)
-        print '1//2 = {}'.format(1//2)
+        print('1/2 = {}'.format(1/2))
+        print('1//2 = {}'.format(1//2))
RefactoringTool: Files that need to be modified:
RefactoringTool: project/base.py
RefactoringTool: project/libs/base.py
RefactoringTool: project/libs/sample_class.py
```

#### コードを自動変換
以下のコマンドで、実際にコードを書き換える。

`-w`オプションを付けることで、ファイルを上書きして書き換えてくれる。
実行すると、バックアップファイルも作成される。

```
$ 2to3 -w project/
```

`--nobackups`オプションを付けると、バックアップファイルは作成されない。

```
$ 2to3 -w --nobackups project/
```

#### コード自動変換後の3系での実行結果
書き換えを行ったコードを3系で実行してみる。
エラーは起きなくなった。また、`1/2`の実行結果が`0.5`になった。

参考：[Python2系と3系の違い - 割り算の少数点以下の扱いについて](https://qiita.com/Esfahan/items/85a89e4dbda7181af663#3-割り算の少数点以下の扱いについて)

```
$ python project/base.py
This is a sample.

[division]
1/2 = 0.5
1//2 = 0
```

## 最後に
冒頭でも書きましたが、<font color="red"><b>このツールで変更が必要な箇所全てを網羅できるわけではないので、</b></font>動作確認をしながら修正必要箇所を見つけていく、という作業は必要になります。

## 関連記事
- [【Python2to3アップデート方法】futurizeで安全にPython2系コードを3系用に自動変換](https://qiita.com/Esfahan/items/6c239a759fd0bd07b107)

## 参考
- [2to3 - docs.python.org](https://docs.python.org/ja/3/library/2to3.html)