イントロ
▄▄▄▄▄
▀▀▀██████▄▄▄ _______________
▄▄▄▄▄ █████████▄ / \
▀▀▀▀█████▌ ▀▐▄ ▀▐█ | Gotta go fast! |
▀▀█████▄▄ ▀██████▄██ | _________________/
▀▄▄▄▄▄ ▀▀█▄▀█════█▀ |/
▀▀▀▄ ▀▀███ ▀ ▄▄
▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌
██▀▄▄▄██▀▄███▀ ▀▀████ ▄██
▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███ ▌▄▄▀
▌ ▐▀████▐███▒▒▒▒▒▐██▌
▀▄▄▄▄▀ ▀▀████▒▒▒▒▄██▀
▀▀█████████▀
▄▄██▀██████▀█
▄██▀ ▀▀▀ █
▄█ ▐▌
▄▄▄▄█▌ ▀█▄▄▄▄▀▀▄
▌ ▐ ▀▀▄▄▄▀
▀▀▄▄▀
そもそもPythonでwebアプリをつくろうという選択肢が辛いですが、それでもPythonでやりたいんだ!という人向けに書きます。
あとsanicの日本語記事が少ないのでTOしていきたい。
sanicってなに
Sanic is a Flask-like Python 3.5+ web server that's written to go fast. It's based on the work done by the amazing folks at magicstack, and was inspired by this article.
On top of being Flask-like, Sanic supports async request handlers. This means you can use the new shiny async/await syntax from Python 3.5, making your code non-blocking and speedy.
意訳をすると
- Flaskライクだよ!
- uvloop使って高速で動くwebサーバーだよ!
- Python3.5+だよ!
ということです。(多分)
Flaskじゃいけないの?
Flaskでもuvloopを使って高速化することはできます。
が、別途ライブラリ入れたりしなくて面倒ではあります。
(そもそもFlask自体多機能化はせずにシンプルに実装して必要に応じでライブラリを入れるという設計理念があったはず)
依存関係やらなにやら色々めんどうなことはできるだけ関わりたくないので標準ではいっているsanicを使えば非同期で高速な処理を行えつつ、Flaskライクで学習コストが低いのが魅力です。
性能がどれくらいすごいのかというのはすでに検証している方がいらっしゃるので画像を引用させていただきます。
細かい数値はリンク先を参照してください。
並列処理の高負荷だとsanicじゃなくてuvloopすげーとなりますが、結局はサクッと作ってさっとデプロイするとなるとSanicの方が良いのかなと思います。
メリットばっかり言ってもフェアじゃないのでデメリットとして以下の点があります(Flaskと比べて)
- 実績が少ない
- 日本語ドキュメントが無い
- 結局Flaskに戻る
1は歴史的に浅いので仕方がないの一言に終わります。
2に関しては公式ドキュメントを読めや!英語読めや!でも良いんですが、初学者が触るには日本語ドキュメントがあったほうが入り口が広がって良いことですし、母国語が日本語なので日本語で読んだほうが翻訳リソースを割かなくて嬉しい。
3に関してはコミュニティがFlaskに比べて小さいのであれもやろう、これもやろうとなったときに詰むのでなんだかんだでFlaskに戻ってしまうということがあります。webをPythonでやろうと思ってるpythonianなら自前で実装してしまうことが多いでしょうが、やはりそこがボトルネックになるのは辛いものがあります
誤解を生まないようにここで述べておきますが、Githubのstar数はFlaskの方が圧倒的に上ですが、(僕が勝手に思っているだけですが)アクティブなコミュニティ活性度の指標となるOpenIssueは俄然sanicの方が多いです。
Hello World
簡単なHello WorldはQiitaの記事を貼って終わりにしたかったんですが、一個もないので書きます。
install
pyenv
で環境を離しておいた方がいいですよ。
そのあたりはこの記事の本質ではないので貼って終わりにします。
注意点は最初に書きましたがPythonのバージョンは3.5+にしてください。
Pyenvの使い方 - Qiita
pyenvのインストール、使い方、pythonのバージョン切り替えできない時の対処法 - Qiita
準備ができたらsanicのインストールです
pip install sanic
以上です。
表示させてみる
READMEのまんまをコピペします。名前はなんでも良いんですが、ここではmain.py
とでもしておきましょう
from sanic import Sanic
from sanic.response import json
app = Sanic()
@app.route('/')
async def test(request):
return json({'hello': 'world'})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
できたら早速動かしてみましょう
python main.py
Ctr+cでサーバーが止まります
以上です。
各パーツの説明
Hello Worldだけだと寂しいので各パーツの説明をしていきます。
かといって全部説明するのは難しいので適時該当箇所の公式ドキュメントのURLを貼っておくので読んでください。
import
from sanic import Sanic
from sanic.response import json
Sanicをimportしています。
import fromがわからない人むけに説明すると、pythonでライブラリを使うときは import ライブラリ名
となります。
じゃぁfromって何だよ。となるので from ライブラリ名 import 関数名
と返します。
上記の例だと from sanic import Sanic
だとsanicライブラリのSanicを読み込んでいる。(それはそう)
二段目はsanic
の中にあるresponse
クラスを呼び出して、その中のjson
という関数を呼び出している。
具体的にいうとGithubでいうところの↓を呼び出している。
https://github.com/channelcat/sanic/blob/master/sanic/response.py
あれ?ここの挙動おかしいぞ?となったらソースコードを眺めてみよう。呼び出されている関数がどういう挙動をしているか理解できればその疑問も解決するはず。
インスタンス
app = Sanic()
インスタンスってなに?と言われると時間が無限にたりないので各自ググってください。
雑でもいいなら"importしたSanicをappに入れている"という理解で3割ぐらい合ってます
デコレーター
@app.route('/')
なかなか見慣れない@ですが、これらはデコレーターというものです。
デコレーターに関しては記事を貼って終わりにします。
Pythonのデコレータについて - Qiita
Pythonのデコレータを理解するための12Step - Qiita
Routing
雑で良いなら、括弧で囲まれた場所がRoutingを指しているというところだけ理解できればなんとかなります。
例えばさっきのmain.pyを以下のように書き換えてみます。
from sanic import Sanic
from sanic.response import json
app = Sanic()
@app.route('/')
async def test(request):
return json({'hello': 'world'})
@app.route('/api/')
async def apiindex(request):
return json({'API': 'Index'})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
先程のように python main.py
を行い http://localhost:8000 にアクセスしてみましょう。
先ほどと同じく {"hello":"world"}
が出たと思いますが、 次は http://localhost:8000/api にアクセスしてみましょう
@app.route('/api/')
の真下に書かれたjson({'API': 'Index'})
が返ってきてきます。
このように@app.route()
の括弧の部分をURIとして指定してあげればそれに応じたリクエスが返ってきます。
動的なRouting
数ページの静的なページだったらこれで問題ないのですが、動的なRoutingの必要が出たときにその分の@app.route()を作るのはしんどいので変数に入れてあげましょう。
from sanic import Sanic
from sanic.response import json
from sanic.response import text
app = Sanic()
@app.route('/')
async def test(request):
return json({'hello': 'world'})
@app.route('/api/')
async def apiindex(request):
return json({'API': 'Index'})
@app.route('/api/<path>')
async def path_handler(request, path):
return text('path = ' + path)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
しれっとfrom sanic.response import text
が加わってますが後ほど説明します
大きな追加点は
@app.route('/api/<path>')
async def path_handler(request, path):
return text('path = ' + path)
で、見たとおりなんですが('')
内において、<>
でくくる事によってその囲った部分が変数となります。
POSTや型
型っぽいのを宣言できたり、POSTを受け取るときもこの部分を編集します。
@app.route('/number/<integer_arg:int>')
async def integer_handler(request, integer_arg):
return text('Integer - {}'.format(integer_arg))
@app.post('/post')
async def post_handler(request):
return text('POST request - {}'.format(request.json))
static file
これだけあれば大抵のことができそうですが、少し問題点が。
現状、URLと返すリソースが1対1となっています。
webサイトを作ろうとした時CSSやjsを書くと思うのですが、このままだとcssやjsまで1回ずつ書かないといけなくなってしまいます。
そこでstaticなファイルはフォルダでまとめて置いておく機能があるので利用しましょう。
以下のようなフォルダ構成を想定します
.
├── index.html
├── main.py
└── static
├── css
└── js
└── main.js
雑にapp.static('/static', './static')
を貼るだけで返したhtmlに
<script type="text/javascript" src="/static/js/main.js"></script>
と記述すると読み込んでくれます。
https://sanic.readthedocs.io/en/latest/sanic/static_files.html
https://github.com/channelcat/sanic/blob/master/sanic/static.py
WebSocket
力が尽きたのでパスします
https://sanic.readthedocs.io/en/latest/sanic/routing.html#websocket-routes
https://github.com/channelcat/sanic/blob/master/sanic/websocket.py
Response
ようやくサーバーに問い合わせて返ってくる内容について触れます。
富士山で言うなら五合目あたりでしょうか。
main.pyのこの部分
return json({'hello': 'world'})
sonicでは以下の8つのfile responseをサポートしています
Plain Text
HTML
JSON
File
Streaming
Redirect
Raw
公式ドキュメントには
from sanic import response
@app.route('/text')
def handle_request(request):
return response.text('Hello world!')
とありますが、個人的には
from sanic.response import text
@app.route('/text')
def handle_request(request):
return text('Hello world!')
の方がキレイかなと思います。(やっている動作自体に変わりはないです)
jsonに関してjson文字列をresponse.json()
に入れるとダブルクォーテーションの前にバックスラッシュが入るバグっぽいのが手元で起こって泣く泣くtextで返したりしています。
Deploy
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
はい、ここです。
if __name__ == '__main__':
ってなんですか
[Python] if __name__ == '__main__' について理解しよう - Qiita
29.4. __main__ — トップレベルのスクリプト環境 — Python 3.6.5 ドキュメント
要はこのmain.py
が実行されたときにしか呼ばれませんよ〜ということです。
app.run()
こいつがメインなんですが、オプションは以下のとおりです
オプション | デフォルト値 | 説明 |
---|---|---|
host | "127.0.0.1" | サーバー名 |
port | 8000 | ポート番号 |
debug | False | デバッグ出力の可否 |
ssl | None | SSL対応させるか |
sock | None | websocketの受け入れ |
workers | 1 | 動かすワーカー数 |
loop | None | asyncioでループ処理させるか |
protocol | HttpProtocol | プロトコル |
https://sanic.readthedocs.io/en/latest/sanic/deploying.html
https://github.com/channelcat/sanic/blob/master/sanic/app.py#L651
サンプル集とか
公式のGithubにいっぱいサンプルがあるのでそれっぽいやつを眺めてみると良いのではないでしょうか
https://github.com/channelcat/sanic/tree/master/examples
https://github.com/channelcat/sanic/wiki/Extensions
https://github.com/channelcat/sanic/wiki/Examples
まとめ
Flaskを触ったことある人ならお気づきですが大体Flaskと同じです。
個人的には初心者はある程度ナウい機能がデフォで入ってるsanicを使って限界を感じたらFlaskに行けばいいじゃないんですかね。
あと好きなsonicは、東京エンカウントで悠木碧さんが好きなゲームを聞かれて答えるモノマネをした杉田智和の「そ゛に゛っ゛く゛ー゛」です。
次回はsanicのテンプレート(Jinja2)で動的ページ作ったり、sanic × vue.jsでイケイケ爆速SPA作ったりする記事を書ければなと思います。
最後にREADMEから注意すべき部分を引用して終わります
TODO
http2
Limitations
No wheels for uvloop and httptools on Windows :(