1. Qiita
  2. 投稿
  3. Python

Pythonをまじめに書こうと思って作った開発環境

  • 17
    いいね
  • 0
    コメント

こんにちは。ara_ta3 です。
この前あるイベントでPythonを書くことになったので、
そのときに作ったローカル開発環境の話をします。

概要

普段Pythonを書かないのですが、書く機会があったので開発環境を揃えた話をします。
こんな風にすると良いよとかありましたらコメントいただければ幸いです。

準備

Python3.5でコーディング規約をみたり、Testしたり、Lintしたりしました。
もろもろはMakefileに書きました。
主にここに書いているmake targetを使っていく感じです。

pip=bin/pip
python=bin/python
flake8=bin/flake8
pytest=bin/pytest

run: $(python)
    $< main.py

test: $(pytest)
    $< -v ./main.py

lint: $(flake8)
    $< main.py

install: $(pip)
    $< install -r requirements.txt

$(pytest): $(pip)
    $(MAKE) install

$(flake8): $(pip)
    $(MAKE) install

$(python): 
    virtualenv . -p python3

$(pip): 
    virtualenv . -p python3

clean:
    rm -rf ./bin
    rm -rf ./lib
    rm -rf ./include

まずは準備。
pipでライブラリなどをインストールした際にglobalに入ったりしないように
virtualenvでカレントディレクトリに諸々をインストールするようにします。
コマンドは virtualenv . -p python3 ですね。
python3.3から venv という環境作成用のコマンドが入ってるらしいので、
そちらでもよかったかもしれません。
http://docs.python.jp/3/library/venv.html

コーディング規約とLint

準備のあとはとりあえずざっとコーディング規約を見てみました。
https://pep8-ja.readthedocs.io/ja/latest/

それっぽいコードを書くときはコーディング規約大事ですよね。
もちろん人間が気にしないようにエディタの設定を頑張りたいですが、
知っておきたいところかと思います。
超ざっくりまとめると・・・

みたいなところを見たりしました。
全部見たほうがいいかなと思いましたが、さっさとコード書きたかったので、
規約にそっていないと怒ってくれるツールを入れて、
そのときに「え、なんでこれダメなの?」みたいなことにならない程度まで読みました。
これらを自分で気をつけながら書くのつらいので、ツールを探してみました。

参考: http://blog-ja.sideci.com/entry/python-lint-pickup-5tools

初めpep8を入れましたが、最終的にはflake8を使いました。
理由はpyflakeも使いたかったのでそれも同時にやってくれそうなflake8にしました。
ちなみにpyflakeはimportしているのに使っていなかったり、
変数宣言しているのに使っていなかったりした際に怒ってくれるものです。

例えばこんなコードがあったときに

def hoge(arg: str):
    print(arg)






def fuga():
    print("bbb")

flake8を実行するとLintのエラーが出てくれます。
エディタの方で保存時にLintが走るようにすれば編集中に怒ってくれたりして便利ですね。
ほんとは自動でやってくれたら便利なんですが、今はやっていません。

$bin/flake8 ./main.py
./main.py:9:1: E303 too many blank lines (6)
make: *** [lint] Error 1

Test

テストはpytestを使いました。
特にあんまり考えてないですw
(なんか絶対こっちのほうが良いよ!とかあったら教えて頂きたいです・・・)
下記みたいなコードがあった際に pytest main.py ってやると

def hoge(arg1: str, arg2: str):
    return arg1 + arg2


def fuga():
    print("bbb")


def test_hoge():
    actual = hoge("aaa", "bbb")
    assert actual == "aaabbb"

pytest main.py ってやると下記みたいになります

bin/pytest -v ./main.py
================================= test session starts ==================================
platform darwin -- Python 3.5.1, pytest-3.0.4, py-1.4.31, pluggy-0.4.0 -- /Users/a-tanaka/advent/bin/python3.5
cachedir: .cache
rootdir: path/to/rootdir, inifile:
collected 1 items

main.py::test_hoge PASSED

=============================== 1 passed in 0.01 seconds ===============================

型ヒント

Python3.5から型ヒントが入っていると聞いたので使ってみました。
引数だけ見るとどうみてもScalaです。おかげで何度か {} でくくってしまいました。
下記スクリプトはPythonを書くきっかけになったイベントで書いたコードの一部です。
(長いですが最後のOrderServiceというクラスだけ見て頂ければと・・・)

from typing import Dict, List, Tuple


class Menu(object):
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def __eq__(self, other):
        return self.name == other.name

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        return hash(str(self))

    def __str__(self):
        return self.name

    def is_pizza(self):
        return type(self) == PizzaMenu


class PizzaMenu(Menu):
    def __init__(self, name, size, price):
        super(PizzaMenu, self).__init__(name, price)
        self.size = size

    def __str__(self):
        return str(self.name + self.size)


class SideMenu(Menu):
    def __init__(self, name, price):
        super(SideMenu, self).__init__(name, price)


class MenuRepository():
    def __init__(self, master: Dict[str, Menu] = {}) -> None:
        self.master = master

    def find_by_name(self, name: str) -> Menu:
        return self.master.get(name, None)


class OrderService():
    def __init__(self, menu_repository: MenuRepository) -> None:
        self.menu_repository = menu_repository

    def order(self, menu_names: List[str]) -> Tuple[List[Menu], List[str]]:
        menus = []
        not_found_menus = []
        for name in menu_names:
            menu = self.menu_repository.find_by_name(name)
            if menu is not None:
                menus.append(menu)
            else:
                not_found_menus.append(name)
        return (menus, not_found_menus)

このOrderServiceというクラスのorderというメソッドは
メニューの名前のリストを受け取り、
存在しているメニューのリストと、存在していなかったメニュー名のリストのタプルを返す。
みたいなちょっと返り値の型が複雑なAPIです。
(この設計が良いかは置いとくと・・・)複雑ですが、
書くことで何が返ってくるのか他の人が見てもわかるので便利ですね。
リストやタプル、ディクショナリから自前のクラスも型ヒントに利用できるので便利!

ただ、これ実は型ヒント書いていたとしても特にエラーになりません\(^o^)/
例えば、stringの引数を2つ受け取る関数があったとして、それにintの与えて実行してみます。

def hoge(arg1: str, arg2: str):
    return arg1 + arg2


print(hoge(1, 2))
$python main.py
3

正常終了です^q^
なので、今のところ型ヒントを与えてもPython自体では特にエラーなどにはなりません。
そこでmypyというライブラリを利用します。
http://mypy-lang.org/

mypyについてはこのあたりを参考に・・・
http://qiita.com/t2y/items/2a1310608da7b5c4860b

mypyを先程のコードに対して実行すると下記のように、
strが入るはずなのにintが入ってるぞみたいなエラーが出てくれます。
便利ですね。
エディタで保存時にmypyがかかるみたいな設定がされていれば、
引数におかしな値を入れていることがわかってエラーが防げそうです。

$mypy main.py
main.py:5: error: Argument 1 to "hoge" has incompatible type "int"; expected "str"
main.py:5: error: Argument 2 to "hoge" has incompatible type "int"; expected "str"

まとめ

  • ローカル開発環境の話をしました。
  • コーディング規約を自動でチェックしてくれるのは flake8 が便利っぽい
  • 型ヒント + mypyで型をチェックすると便利
  • Python + 型ヒント => 見た目Scalaっぽい
  • Pythonまた書くとき色々探していきたいと思います。