みろりHP


緑色さんの多目的ブログ
みろりえいちぴー
ごゆるりとおくつろぎあさーせ。
| カテゴリ:プログラミング |
Python python3でウェブサーバを作る(Bottleでリトライ)



一年前、pythonでウェブサーバを作ろうとしたことがあったんだけれど、道半ばで挫折した。確かcgiとかいう機能を使ったり、pyスクリプトとテンプレートを分けたり、中途半端に非同期処理を組み込んだりした挙句、日本語表示がうまくいかなくてイヤになってヤメちゃったんだよね。そのリトライをするぞ。あれから一年、趣味とはいえきちんとクラスを覚え、ちっちゃなものではあるがゲームを作り、Skype Botを作り、DialogFrameなんつー俺俺フレームワークまで作ったのだ。今ならばいける! かもしれない!
というわけで「1日1python」のお時間です。今回は前回のhttp.serverではなく、Bottleモジュールを使ってウェブサーバを作る。
先回のhttp.serverをもう一度触り直してみたのだけれど、「pythonを使ってページを表示するにはどうしてもURLが/cgi-bin/になっちまう。もっと自由にURLを決めたい」「htmlテンプレートの中でpython側の変数とか処理をかけない」なんつー不満点があったのよな。でもBottleがすべて解決してくれた。



目次。
0. とりあえずこれ書いとく
#!/usr/bin/env python
# coding: utf-8
from bottle import route, run, template, request
from bottle import error, redirect, HTTPResponse, static_file

# これは一番下に
run(host='localhost', port=8000, debug=True, reloader=True)
以降のスクリプトは全部この中に書く。なお localhost を 0.0.0.0 にすると、サーバが外からも見えるようになるようだぜ。俺はそうやって、スマホからチェックをしていた。

1. 一番シンプルなやつ
@route('/1/')
def page_01(name):
    return '<h1>YO!</h1>'
http://localhost:8000/1/ でアクセスできる。

2. URLにワイルドカードを使う
@route('/2/:name')
def page_02(name):
    return f'<h1>YO {name}!</h1>'
http://localhost:8000/2/Wada とかでアクセスできる。

3. テンプレートを使う
python側がこう。テンプレート側へ変数を渡すにはこういう書き方をする。
@route('/3/')
def page_03():
    a = 'LALALA'
    b = {'0':'AAA', '1':'BBB'}
    return template('template_03', a=a, b=b)
テンプレートはpythonスクリプトと同階層に
views
というディレクトリを作ってその中に置く。以下にpythonから受け取った変数の表示方法とかテンプレート内pythonスクリプトの書き方とかを記載する。
# template_03.tpl

■ pythonスクリプトから受け取った変数の表示
ふつーの変数: {{a}}
ディクショナリとか: {{b['0']}}

■ for文
<ul>
% for i in range(5)
    <li>{{i}}</li>
% end
</ul>

■ if文
% if b['1'] == 'BBB':
    <p>...</p>
% else:
    <p>...</p>
% end

■ いちいち % を行頭につけるのが面倒なアナタには
<%
    num = len(b)
    if num in [1]:
        pass
    elif num in [2]:
        pass
    end
%>

4. GETクエリを使う
@route('/4/')
def page_04():
    # get一覧を見たいならこれ
    print(request.query.__dict__)

    # クエリの取り出し方
    get_content = request.query.a

    return template('template_04', get_content=get_content)
テンプレート側は普通のgetフォームなので割愛。

5. POSTクエリを使う
@route('/5/')
@route('/5/', method='POST')
def page_05():
    # post一覧を見たいならこれ。
    print(request.params.__dict__)

    # 取り出し方。
    post_content = request.forms.get('name')

    return template('template_05', post_content=post_content)
テンプレート側は普通のpostフォームなので割愛。POSTのほうは、route()デコレータ表記をふたつ書かないといけないのに注意。

6. リダイレクトする
@route('/6/')
def page_06():
    return redirect('/5/')

7. ステータスコードでハンドリングする
えっとぶっちゃけ俺はステータスコードなるものは404くらいしかしらんので404の例を。存在しないURLにアクセスしたら、/5/にリダイレクトするようにしてみた。
@error(404)
def error_404(error):
    r = HTTPResponse(status=302)
    r.set_header('Location', '/5/')
    return r
errorを使った場合、6で使用したredirect()は使えないみたい。でも上述の書き方をしたらむりくりリダイレクトできた。HTTPヘッダにステータスコード302(リダイレクト)と飛び先を設定する方式。

8. 静的ファイルを使う
pyファイルと同階層に静的ファイルを置くディレクトリ(例では
static
)を作り、ソコを静的ファイル置き場として登録する。
@route('/static/:file_path')
def static(file_path):
    return static_file(file_path, root='./static')
HTMLから呼び出すときはこんな感じの相対パスで。
<img src="/static/image/python3.png">



こんなところで俺のリトライは無事成功した。自身の成長を感じられてふんすふんすしつつ改めて先回の奮闘を見ると、いやあpython全然関係ないところで詰まってんなあ。「ネットに情報がねえ」なんて理由で挫折しているが、まあ多分それは、当時もBottleとかDjangoとかそういう単語自体は目に入ってたんだろうけど、馴染みのないモジュールの蓋を開けてみることにまだ抵抗があったのだろう。ちなみにBottleの前にDjangoも試してみたが、あっちはなんかスゲー規模がでかいわディレクトリに色々ファイルが増えやがるわでタイヘンだったので、import一本で済むBottleを今回は採用した。
使用をヤメたhttp.serverについては、使うのがhtmlだけだったら即採用だと思うぜ。なにせターミナルから一行で立ち上がるからな。
$ python -m http.server ポート番号
学習の集大成として、小さなジャンケンゲームを作ったので下に載っけとく。スクリプトと、htmlを載せたところで、あっそうかhtmlを載せるってことはcssも載せないと締まらないのか…クソ長くなっちゃう…ヤだ…ってなったので折りたたんどく。




こんなゲーム。

続きを読む >>
| 緑色 | プログラミング | comments(0) |
| カテゴリ:プログラミング |
Python デコレータ奮闘記



見たことない構文が目に留まった。
@property
def foo(self):
    なんちゃらかんちゃら
なんだこの@は? 調べたところによるとデコレータっていうものらしい。ひとつ覚えてみるかというわけで「1日1python」のお時間です。



どうもデコレータは機能ってよりプログラムの書き方の技術のひとつみたいだな。ある汎用的な関数に何か機能を追加したいときに用いる技術だ。上書きじゃなくて、追加。上書きしたいんだったら単に定義しなおせばいいわけだもんね。関数をまるごと上書きするんじゃもったいないから少しずつデコレーションして使おうってことな。ほんで、汎用的関数をデコレーションするために使う関数のことをデコレータっていうわけだ。そういうことなら話が早い。実際に書いてみるぜ。
今回の目次



クラスとインスタンスを使ったデコレータを書いてみる

まず汎用的なものを作る。
class Foo:
    def __init__(self, name):
        self.name = name

    def call(self):
        return self.name
名前を与えてインスタンスを作ってcall()を呼べば、その名前を返してくれるクラスね。以下のように使う。
name = 'William Forsyth'
instance = Foo(name)
print(instance.call())
# William Forsyth と表示される。

使ってるうちに、名前だけじゃちょっとさみしいから枠線をつけたくなった。枠線でデコレーションをする、Deco_Wakusenデコレータを作る。上のクラスの続きに記述する。
class Deco_Wakusen:
    def __init__(self, instance):
        self.instance = instance

    def call(self):
        ret = ''
        ret += '--------------------¥n'
        ret += '  ' + self.instance.call() + '¥n'
        ret += '--------------------'
        return ret
これは上で作ったFooのインスタンスを与えてインスタンスを作る。イメージとしてはチョコレートを糖衣で包む感じか。糖衣であるDeco_Wakusenにもcall()メソッドを準備してある。このcall()では元のFooインスタンスの結果を使いつつ、枠線をつけて返すようにしてある。別にクラスを継承してるわけじゃないから、上書きにもならない。デコレーションしてるだけ。以下のように使うと、上のとおんなじようにcallしてるはずなのにデコレーションされたものが飛び出してくるぜ。
instance_wakusen = Deco_Wakusen(instance)
print(instance_wakusen.call())
# 以下のように表示される
--------------------
  William Forsyth
--------------------

今度は枠線じゃなくて、名前を大文字にして表示したくなった。やることは同じだ。
class Deco_Oomoji:
    def __init__(self, instance):
        self.instance = instance

    def call(self):
        return self.instance.call().upper()
使い方は枠線のときと同じ。
instance_oomoji = Deco_Oomoji(instance)
print(instance_oomoji.call())
# WILLIAM FORSYTHと表示される。

それでデコレータの楽しいトコなんだけど、メソッドを上書きしてるわけじゃないから、続けざまにデコレーションをすると単純に機能が追加されていくのだよ。まず汎用物を作って、枠線デコレータでデコレーションして、さらに大文字デコレータでデコレーションしてみる。
instance_wakusen = Deco_Wakusen(instance)
instance_wakusen_oomoji = Deco_Oomoji(instance_wakusen)
print(instance_wakusen_oomoji.call())
# 以下のように表示される。
--------------------
  WILLIAM FORSYTH
--------------------
ビューリホー。新しい機能を使ってるわけでも、なんでもない。ただのロジックの組み方で面白い機構ができた。

上述の例ではわかりやすさのためいちいちインスタンス名を変えてたけれど、同じインスタンス名を使ってもOK。使用感がまったく変わらないままデコレートした結果が飛び出してくる。テンプレートとしてまとめるとこんな感じか?
class 汎用:
    def デコレーションされるメソッド(self):
        なんちゃらかんちゃら

class デコレータ:
    def __init__(self, instance):
        self.instance = instance

    def デコレーションするメソッドと同名のメソッド(self):
        self.instance.メソッド()を使用しつつ
        前後に機能を書き加えて結果をreturn

instance = 汎用()
instance = デコレータ(instance)
instance.デコレーションされたメソッド()



クラスはちと大袈裟だからふつーの関数で書いてみる

いやお前いちいちクラス使うことはねーだろということで関数でやってみる。
def call(name):
    return name
シンプルどころの話じゃない。
name = 'William Forsyth'
print(call(name))
# William Forsyth

枠線デコレータとしてはこういう関数を用意。
def deco_wakusen(func):
    def call_kari(name):
        ret = ''
        ret += '--------------------¥n'
        ret += '  ' + func(name) + '¥n'
        ret += '--------------------'
        return ret
    return call_kari
関数バージョンはクラスよりシンプルになるかと思いきや、ネストのある関数が登場しちまった。けれどやってることはクラスバージョンと同じで、「もともとの関数を引数にして、内部でその関数をデコレーションした関数を作成し、その関数を返」してるだけ。関数を呼び出してる側からすれば、「関数投げたら機能追加されて返ってきたわ」みたいな感じ。
call_wakusen = deco_wakusen(call)
print(call_wakusen(name))
# こうなる。
--------------------
  William Forsyth
--------------------

大文字デコレータ。
# 元の関数を投げて、
def deco_oomoji(func):
    # 元の関数を利用しつつデコレーションする関数を作って、
    def call_kari(name):
        return func(name).upper()
    # デコレート済みの関数を返す。
    return call_kari
使い方。
call_oomoji = deco_oomoji(call)
print(call_oomoji(name))
# WILLIAM FORSYTH

重ねがけ。
call_wakusen = deco_wakusen(call)
call_wakusen_oomoji = deco_oomoji(call_wakusen)
print(call_wakusen_oomoji(name))
# こうなる。
--------------------
  WILLIAM FORSYTH
--------------------

まとめるとこう。
def デコレーションされる汎用関数():
    なんちゃらかんちゃら

def デコレータ関数(func):
    def ここの名前はどうでもいい():
        funcを使用しつつ
        前後に機能を書き加えて結果をreturn
    作った関数をreturn

汎用関数 = デコレータ関数(汎用関数)
汎用関数()



@を使って書いてみる

おいデコレータ実現できちゃったじゃねーか。どこにも@が出てこないぞ、というわけなんだが、これは上述のスクリプトを簡単に書くために使えるようだ。具体的には以下。
@deco_wakusen
def call(name):
    return name
こういう風に書くと、call()関数がdeco_wakusenデコレータでデコレーションされたことになるようだ。大文字のほうも重ねがけするならこう。
@deco_wakusen
@deco_oomoji
def call(name):
    return name
直後にこう使える。
print(call(name))
# 結果は以下。
--------------------
  WILLIAM FORSYTH
--------------------

というわけでな、冒頭で俺の目に留まった記述は、関数foo()の中身を、どっかにあるproperty()という関数で加工しているんだよーということを表していたわけだ。これの直後にfoo()を実行したら、fooに書いてあることそのままじゃなくて、propertyでデコレーションされた結果が返ってくるのだろう。いやー、調べる前に実行していたらえらい混乱が俺を襲っていたぜ、ハッハッハ。
@property
def foo(self):
    なんちゃらかんちゃら

# 実行したら、「なんちゃらかんちゃら」じゃなくて
# property()で加工されたなんちゃらかんちゃらが
# 起こる。(たぶん)



さて、みっつの章に分けてまとめてみたわけだが、最初のふたつと@使用バージョンでは趣が異なるのが気になる。俺がデコレータという概念を調べて実際に試してみた「クラスバージョン」と「関数バージョン」は、「まず汎用的な関数があり、それをあとから目的に合わせて異なるパッチを作って当てていく」という状況想定で書いてある。いっぽう@でデコレーションを簡易化しちゃうぜバージョンは、「まず汎用的なデコレータがあり、それを必要に応じて装着していく」という状況を想定しているように見える。たとえばデコレータが「関数実行結果のログをとっておく」みたいな内容だとしたら断然後者のほうが使いやすいだろう。でも前者の登場機会だっておんなじくらいあるんじゃねえ? 俺が何に引っかかってるかっていうとさ、「汎用関数が最初にある」型と「汎用デコレータが最初にある」型のうち、後者にだけ@を使った省略的書き方があるのはなんでなんだろう? ってとこなんだよ。俺がデコレータという概念を知って自然に発想した型が前者だったから、余計にね。これについては特に答えが見つかんなかった。まあ当初の目的は果たせたのでグーとする。1日1pythonどころか、このスクリプト完成にこぎつけるのには一週間くらいかかっちゃったけどこれにて閉幕。




| 緑色 | プログラミング | comments(0) |
      1
2345678
9101112131415
16171819202122
23242526272829
30      
<< April 2017 >>
+ みろりHP内検索
+ 閲覧記事
+ カテゴリ
+ 年月選択
  • 2017年 04月 (8)
  • 2017年 03月 (7)
  • 2017年 02月 (10)
  • 2017年 01月 (6)
  • 2016年 12月 (8)
  • 2016年 11月 (8)
  • 2016年 10月 (5)
  • 2016年 09月 (5)
  • 2016年 08月 (7)
  • 2016年 07月 (9)
  • 2016年 06月 (6)
  • 2016年 05月 (8)
  • 2016年 04月 (10)
  • 2016年 03月 (10)
  • 2016年 02月 (8)
  • 2016年 01月 (9)
  • 2015年 12月 (9)
  • 2015年 11月 (6)
  • 2015年 10月 (5)
  • 2015年 09月 (4)
  • 2015年 08月 (8)
  • 2015年 07月 (5)
  • 2015年 06月 (3)
  • 2015年 05月 (7)
  • 2015年 04月 (8)
  • 2015年 03月 (12)
  • 2015年 02月 (8)
  • 2015年 01月 (4)
  • 2014年 12月 (5)
  • 2014年 11月 (5)
  • 2014年 10月 (7)
  • 2014年 09月 (4)
  • 2014年 08月 (7)
  • 2014年 07月 (6)
  • 2014年 06月 (4)
  • 2014年 05月 (12)
  • 2014年 04月 (9)
  • 2014年 03月 (6)
  • 2014年 02月 (6)
  • 2014年 01月 (8)
  • 2013年 12月 (7)
  • 2013年 11月 (10)
  • 2013年 10月 (10)
  • 2013年 09月 (9)
  • 2013年 08月 (11)
  • 2013年 07月 (10)
  • 2013年 06月 (9)
  • 2013年 05月 (15)
  • 2013年 04月 (11)
  • 2013年 03月 (5)
  • 2013年 02月 (7)
  • 2013年 01月 (6)
  • 2012年 12月 (9)
  • 2012年 11月 (10)
  • 2012年 10月 (10)
  • 2012年 09月 (4)
  • 2012年 08月 (2)
  • 2012年 07月 (7)
  • 2012年 06月 (13)
  • 2012年 05月 (13)
  • 2012年 04月 (15)
  • 2012年 03月 (4)
  • 2012年 02月 (12)
  • 2012年 01月 (9)
  • 2011年 12月 (5)
  • 2011年 11月 (13)
  • 2011年 10月 (2)
  • 2011年 09月 (2)
  • 2011年 08月 (1)
  • 2011年 06月 (1)
  • 2011年 05月 (4)
  • 2011年 04月 (10)
  • 2011年 03月 (8)
  • 2011年 02月 (11)
  • 2011年 01月 (14)
  • 2010年 12月 (14)
  • 2010年 11月 (17)
  • 2010年 10月 (17)
  • 2010年 09月 (19)
  • 2010年 08月 (22)
  • 2010年 07月 (18)
  • 2010年 06月 (16)
  • 2010年 05月 (19)
  • 2010年 04月 (15)
  • 2010年 03月 (22)
  • 2010年 02月 (18)
  • 2010年 01月 (18)
  • 2009年 06月 (2)
  • 2009年 04月 (1)
  • 2007年 12月 (10)
  • 2007年 11月 (7)
  • 2007年 10月 (9)
  • 2007年 09月 (4)
  • 2007年 07月 (5)
  • 2007年 06月 (11)
  • 2007年 05月 (6)
  • 2007年 04月 (4)
  • + ブックマーク
    + 最近のコメント
    + アクセスカウンター
    全体(since 2010.02.03.)
    今日… 昨日…