Python2系でもPython3系でも簡易Web鯖をワンライナーで立てる
背景
jsakamotoさんのMarkdownPresenterにて、
Update readme.md to work with current version of python by AlanKavanagh · Pull Request #8 · jsakamoto/MarkdownPresenter · GitHub
ということに首を突っ込んでPython2系でもPython3系でも動くように模索しました。
↓発端(英語はワカリマセン)
まさか自分が Python の 2 と 3 のバージョン違いに悩まされる羽目になるとは想像もしてなかった。
2014-01-08 12:54:46 via web
Can I write "One" Python code that lauching http server ("python -m SimpleHTTPServer" or "python -m http.server") at both of v2 and v3?
2014-01-08 12:59:35 via web
目的
とりあえず、Pythonのバージョンごとで実行するならば
- Python2系
$ python -m SimpleHTTPServer
- Python3系
$ python -m http.server
で実行できる事を、ワンライナーで両対応させたいとのこと(だと思った)。
試行 その1
ちょこちょこ調べることに、単純にバージョンで切り替える場合、
sys.version_infoが使える。
import sys if sys.version_info < (3, 0, 0): #Python2系 import SimpleHTTPServer as server else: #Python3系 import http.server as server server.test()
これを元にワンライナーしていこうとした。
そこで取り出したるは__import__関数
$ python -c 'import sys;__import__("SimpleHTTPServer"if sys.version_info<(3,0,0)else"http.server").test()'
コレで楽勝!…というわけにも行かず、
Python3の時に、test関数なんてないよ!って言われる。
Traceback (most recent call last): File "<string>", line 1, in <module> AttributeError: 'module' object has no attribute 'test'
原因解決するために対話モードで実行
$ python Python 3.3.0 (default, Jan 8 2014, 13:37:36) [GCC 4.1.2 20080704 (Red Hat 4.1.2-52)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> m = __import__("http.server") >>> dir(m) ['__builtins__', '__cached__', '__doc__', '__file__', '__initializing__', '__loader__', '__name__', '__package__', '__path__', 'client', 'server'] >>> dir(m.server) ['BaseHTTPRequestHandler', 'CGIHTTPRequestHandler', 'DEFAULT_ERROR_CONTENT_TYPE', 'DEFAULT_ERROR_MESSAGE', 'HTTPServer', 'SimpleHTTPRequestHandler', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__initializing__', '__loader__', '__name__', '__package__', '__version__', '_quote_html', '_url_collapse_path', 'argparse', 'copy', 'email', 'executable', 'html', 'http', 'io', 'mimetypes', 'nobody', 'nobody_uid', 'os', 'posixpath', 'select', 'shutil', 'socket', 'socketserver', 'sys', 'test', 'time', 'urllib']
__import__関数だと深い階層までimportしないらしく、上記の対話モードでも
m = __import__("http.server")
とimport指定してるにもかかわらず、dir(m)にてserverが出てきてしまっている。
__import__が一番浅い所(この場合、http)までしか、importできてない。
もう少し調べたら、ワンライナー用にこんな書き方があるぐらいなので、
多分短く書く手法としてはコレ以上やるのは無理そう。
試行 その2
次に2系3系のどっちかなのでSimpleHTTPServerがimportエラー出た時、http.serverインポートすればいいや作戦。
ベースはこんな感じ
try: import SimpleHTTPServer as server except: import http.server as server server.test()
しかしtry-except句を1行でかくとこんなエラーが
>>> try:import SimpleHTTPServer as server except:import http.server as server File "<stdin>", line 1 try:import SimpleHTTPServer as server except:import http.server as server ^ SyntaxError: invalid syntax
と、怒られる。
構文的にtry-except句は
try:import SimpleHTTPServer as server except:import http.server as server
ぐらいしかコンパイラさんは認めてくれない。
ここで取り出したるは「exec」。
execの使用例はさすがは天下(?)のstackoverflowさん。
try-exceptをワンライナーで書くノウハウも有りました。
exec("try:import SimpleHTTPServer as server\nexcept:import http.server as server")
これできちんと動きます。
- Python2系
$ python Python 2.4.3 (#1, Feb 5 2013, 15:50:22) [GCC 4.1.2 20080704 (Red Hat 4.1.2-52)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> exec("try:import SimpleHTTPServer as server\nexcept:import http.server as server") >>>
- Python3系
python Python 3.3.0 (default, Jan 8 2014, 13:37:36) [GCC 4.1.2 20080704 (Red Hat 4.1.2-52)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> exec("try:import SimpleHTTPServer as server\nexcept:import http.server as server") >>>
と、両環境でも動いたので、これを元に書いたワンライナーがこちら
$ python -c 'exec("try:import SimpleHTTPServer as m\nexcept:import http.server as m");m.test()'
Web鯖も起動したしこれにて終了!めでたしめでたし!!
新規問題(StatusCode 501問題)
…かと、おもいきやまだ問題が残ってた。
こちらの話だとGETメソッドの処理がなくて、501が出るとのこと。
確認してみた。
試行 その3
- server側
$ python -V Python 3.3.0 $ python -c 'exec("try:import SimpleHTTPServer as m\nexcept:import http.server as m");m.test()' Serving HTTP on 0.0.0.0 port 8000 ... 127.0.0.1 - - [09/Jan/2014 12:10:42] code 501, message Unsupported method ('GET') 127.0.0.1 - - [09/Jan/2014 12:10:42] "GET /Presenter.html HTTP/1.1" 501 -
- client側
$ nc localhost 8000 GET /Presenter.html HTTP/1.1 HTTP/1.0 501 Unsupported method ('GET') Server: BaseHTTP/0.6 Python/3.3.0 Date: Thu, 09 Jan 2014 05:20:32 GMT Content-Type: text/html;charset=utf-8 Connection: close <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> <title>Error response</title> </head> <body> <h1>Error response</h1> <p>Error code: 501</p> <p>Message: Unsupported method ('GET').</p> <p>Error code explanation: 501 - Server does not support this operation.</p> </body> </html>
改善
Python2系では出なかったけど、Python3系で出ました。
なぜだろうと思ってライブラリの中身を見たら、
SimpleHTTPServer.py のtest関数とhttp/server.py のtest関数のデフォルト引数が若干変わってました。
- SimpleHTTPServer.py
def test(HandlerClass = SimpleHTTPRequestHandler, ServerClass = BaseHTTPServer.HTTPServer):
- http/server.py
def test(HandlerClass = BaseHTTPRequestHandler, ServerClass = HTTPServer, protocol="HTTP/1.0", port=8000):
主に「HandlerClass = SimpleHTTPRequestHandler」の部分が、
「HandlerClass = BaseHTTPRequestHandler」になっていたため、
do_ + HTTP method名の関数がHandlerクラスに定義されていないと501を返すようになっています。
そこで、test関数の引数でハンドラーを指定して対処します。
次のように改良しました。
結果
$ python -c 'exec("try:import SimpleHTTPServer as m\nexcept:import http.server as m");m.test(HandlerClass=m.SimpleHTTPRequestHandler)'
これで正常に動くのを確認しました。
多分、これで大丈夫(棒読)
おまけ
2to3使ってコンバートしたらどうなるかなぁという実験
- hoge.py
# -*- encoding: utf8 -*- import SimpleHTTPServer SimpleHTTPServer.test()
$ 2to3 hoge.py RefactoringTool: Skipping implicit fixer: buffer RefactoringTool: Skipping implicit fixer: idioms RefactoringTool: Skipping implicit fixer: set_literal RefactoringTool: Skipping implicit fixer: ws_comma RefactoringTool: Refactored hoge.py --- hoge.py (original) +++ hoge.py (refactored) @@ -1,7 +1,7 @@ # -*- encoding: utf8 -*- -import SimpleHTTPServer +import http.server -SimpleHTTPServer.test() +http.server.test() RefactoringTool: Files that need to be modified: RefactoringTool: hoge.py
引数まで関与してくれないので、
わざわざtest関数使って実装してる人は少ないとおもいますが、
地味な嵌まりどころかも知れません。