yapfの導入

yapfの導入

個人の開発スタイルのような感じですが、Windowsなどでpythonのソースを書いていて、vimへコピー・ペーストをすることがよくあるのですが、何故かインデントのフォーマットが異なってしまうので、そのあとの手間が多いのでなんとかしないと行けないなあと思っていました。(他の言語ならそれほど大きく問題はありませんが、pythonではそういうわけにも行きません。)

そこで、yapfとういうフォーマッタがあることがわかりました。

github.com

googleさん謹製のようです。

yapfのインストール

readmeを見ればわかりますが以下の様にインストールします。

$ pip install yapf
Collecting yapf
  Downloading yapf-0.17.0-py2.py3-none-any.whl (151kB)
    100% |????????????????????????????????| 153kB 1.1MB/s
Installing collected packages: yapf
Successfully installed yapf-0.17.0

raspberrypiでインストールするとyapf~/.local/bin/yapfにインストールされるようです。

実行するとこんな感じになります。

$ ~/.local/bin/yapf --version
yapf 0.17.0

$ ~/.local/bin/yapf --help
usage: yapf [-h] [-v] [-d | -i] [-r | -l START-END] [-e PATTERN]
            [--style STYLE] [--style-help] [--no-local-style] [-p]
            [files [files ...]]

Formatter for Python code.

positional arguments:
  files

optional arguments:
  -h, --help            show this help message and exit
  -v, --version         show version number and exit
  -d, --diff            print the diff for the fixed source
  -i, --in-place        make changes to files in place
  -r, --recursive       run recursively over directories
  -l START-END, --lines START-END
                        range of lines to reformat, one-based
  -e PATTERN, --exclude PATTERN
                        patterns for files to exclude from formatting
  --style STYLE         specify formatting style: either a style name (for
                        example "pep8" or "google"), or the name of a file
                        with style settings. The default is pep8 unless a
                        .style.yapf or setup.cfg file located in one of the
                        parent directories of the source file (or current
                        directory for stdin)
  --style-help          show style settings and exit; this output can be saved
                        to .style.yapf to make your settings permanent
  --no-local-style      don't search for local style definition
  -p, --parallel        Run yapf in parallel when formatting multiple files.
                        Requires concurrent.futures in Python 2.X

テスト

公式サイトのサンプルを使って実験してみます。

github.com

x = {  'a':37,'b':42,

'c':927}

y = 'hello ''world'
z = 'hello '+'world'
a = 'hello {}'.format('world')
class foo  (     object  ):
  def f    (self   ):
    return       37*-+2
  def g(self, x,y=42):
      return y
def f  (   a ) :
  return      37+-+a[42-x :  y**3]

f:id:ueponx:20170910140531p:plain

$ ~/.local/bin/yapf sample.py
Traceback (most recent call last):
  File "/home/pi/.local/bin/yapf", line 11, in <module>
    sys.exit(run_main())
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/__init__.py", line 296, in run_main
    sys.exit(main(sys.argv))
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/__init__.py", line 188, in main
    parallel=args.parallel)
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/__init__.py", line 236, in FormatFiles
    in_place, print_diff, verify)
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/__init__.py", line 259, in _FormatFile
    logger=logging.warning)
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/yapflib/yapf_api.py", line 91, in FormatFile
    verify=verify)
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/yapflib/yapf_api.py", line 129, in FormatCode
    tree = pytree_utils.ParseCodeToTree(unformatted_source)
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/yapflib/pytree_utils.py", line 102, in ParseCodeToTree
    tree = parser_driver.parse_string(code, debug=False)
  File "/usr/lib/python2.7/lib2to3/pgen2/driver.py", line 106, in parse_string
    return self.parse_tokens(tokens, debug)
  File "/usr/lib/python2.7/lib2to3/pgen2/driver.py", line 47, in parse_tokens
    for quintuple in tokens:
  File "/usr/lib/python2.7/lib2to3/pgen2/tokenize.py", line 429, in generate_tokens
    ("<tokenize>", lnum, pos, line))
  File "sample.py", line 11
    def g(self, x,y=42):
    ^
IndentationError: unindent does not match any outer indentation level

動かない…エラーメッセージから察するに、Webページのものをコピペして使うとpythonの文法まで崩れてしまっているようです。

Teratermのウインドウにドラッグ・アンド・ドロップすることで実行できる、scpなどでファイル転送をすると…

$ cat sample.py
x = {  'a':37,'b':42,

'c':927}

y = 'hello ''world'
z = 'hello '+'world'
a = 'hello {}'.format('world')
class foo  (     object  ):
  def f    (self   ):
    return       37*-+2
  def g(self, x,y=42):
      return y
def f  (   a ) :
  return      37+-+a[42-x :  y**3]

$ ~/.local/bin/yapf sample.py
x = {'a': 37, 'b': 42, 'c': 927}

y = 'hello ' 'world'
z = 'hello ' + 'world'
a = 'hello {}'.format('world')


class foo(object):
    def f(self):
        return 37 * -+2

    def g(self, x, y=42):
        return y


def f(a):
    return 37 + -+a[42 - x:y**3]

うまく実行できたようです。ファイルを変換したものに上書きするには-iをつければいいようです。他にも処理後との差分(diff)を表示する-d再帰的に実行する-rのスイッチがあります。

$ ~/.local/bin/yapf -i sample.py
pi@raspberrypi:~/yapf_sample $ cat sample.py
x = {'a': 37, 'b': 42, 'c': 927}

y = 'hello ' 'world'
z = 'hello ' + 'world'
a = 'hello {}'.format('world')


class foo(object):
    def f(self):
        return 37 * -+2

    def g(self, x, y=42):
        return y


def f(a):
    return 37 + -+a[42 - x:y**3]

REPL環境での実行でモジュールとして使用することもできる様です。 githubのページでは以下のようになると書いてあるのですが、

>>> from yapf.yapflib.yapf_api import FormatCode  # reformat a string of code

>>> FormatCode("f ( a = 1, b = 2 )")
'f(a=1, b=2)\n'

自分の環境では

$ python
Python 2.7.13 (default, Jan 19 2017, 14:48:08)
[GCC 6.3.0 20170124] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from yapf.yapflib.yapf_api import FormatCode  # reformat a string of code
>>> FormatCode("f ( a = 1, b = 2 )")
(u'f(a=1, b=2)\n', True)
>>>

となりました。python2だったからかな?念のためpython3でも実行してみました。

$ pip3 install yapf
Collecting yapf
  Using cached yapf-0.17.0-py2.py3-none-any.whl
Installing collected packages: yapf
Successfully installed yapf-0.17.0

$ python3
Python 3.5.3 (default, Jan 19 2017, 14:11:04)
[GCC 6.3.0 20170124] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from yapf.yapflib.yapf_api import FormatCode
>>> FormatCode("f ( a = 1, b = 2 )")
('f(a=1, b=2)\n', True)
>>> FormatCode("def g( ):\n    a=1\n    b = 2\n    return a==b", lines=[(1, 1), (2, 3)])
('def g():\n    a = 1\n    b = 2\n    return a==b\n', True)

ユニコード文字列を指すのuのプリフィックス以外は変わったところが無いみたい、あれ?

終わりに

コピペしてフォーマットが少し崩れるぐらいであれば、なんとかできるかなと思いましたが、派手に崩れる(文法的に崩れるレベル)とちょっとむずかしい様です。

RaspberryPiだけでなくWindowsのanaconda環境にもインストールしましたがこっちで使ったほうがいいのかもしれません。(コピペする前にこっちで整形をするという感じ)