Pythonを使う理由と作った物(ソース付)

みなさん、Pythonってご存知でしょうか?
ニシキヘビ。。ではないです。
プログラミング言語のPythonです。

↓Pythonのロゴ。蛇が2匹です。

このPython。日本国内では、それほど知名度は高くないのですが、2010年で最も成長したプログラミング言語にも選ばれるなど、急速に人気の高まりつつある言語です。2011年にはPHPとC++を抜かして3位になれそうな勢いです!では、このPython、どうして急に人気がでてきたのでしょうか?理由は大きくわけて3つあると思います。

Pythonが急に普及し始めた理由

  1. GAEで動作する

    これが最大の理由だと思います。Googleのサーバを使って簡単にサービスを提供できます。1日あたり1GBまでの転送なら、料金は一切かかりません ※1。無料利用分を超えて使った場合も、課金設定(1日当たりの支払い額の上限)を設定するだけで、簡単にサーバを増強できます。ロードバランス、データベースのレプリケーション、そんなことは一切考える必要がありません。全部自動でやってくれます。固定費が不要なので、AmazonEC2なんかより、よっぽど敷居は低いです。

  2. 理解しやすさ

    これには異論が多いと思います。C、Java、PHPなどとは似ても似つかないソースコード。でも、Python、慣れてしまえば読みやすいですよ。Pythonはインデントに縛られた言語です。プログラムの構造と見た目(インデント)が必ず一致しているので、誰が書いても似たようなソースコードになります。慣れないうちは思うように書き進められないかもしれませんが、しばらく使い込めば「読みやすいコード」が自然にかけるようになっているはずです。

  3. 遅さは問題ではなくなった

    Pythonはスクリプト言語です。コンパイル言語と比べると桁違いに遅いです。でも、Webアプリケーションのボトルネックって、言語ではないですよね?多くの場合、データベースアクセスや、ネットワーク通信、マルチメディアファイルのダウンロードがボトルネックです。スクリプト言語の遅さが問題になっていたのは10年前の話です。今は、PHPだって、RoRだって、Pythonだって実用上問題ない速度で動作します。1番にもつながりますが、言語の速度ではなく、スケールアウトするかどうか、つまり、サーバの台数に応じてきちんと性能が伸びるかが問題なのです。

※1 CPUの使用時間(無料分: 6.5時間/日)なども課金対象ですが、通信量制限(無料分: 1GB/日)が最も課金対象になりやすいです。

で、使ってみた。

Pythonを使う○○個の理由とか並べてても、実際に使ってみないと説得力がないので、実際につかってみた。ソースコードも全文貼り付けていますが、初心者が書いたコードなのであまり信用しないで頂けると幸いです。

1. サーバ監視

サーバ監視

サーバ監視

GAEからサーバの監視をしてみた。HTTPリクエストの応答時間を計るだけのシンプルなサービスだが、安定して稼動している。社内のサーバは、Zabbixなどを活用してデータセンタ内からきっちり監視しているが、データセンタと外部との回線の障害などに関しては、外部からの監視が有用だ。(スクリーンショットに掲載したデータは私が個人的に借りているサーバものです)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#!/usr/bin/env python

import cgi
import datetime
import wsgiref.handlers
import urllib
import appengine_utilities.sessions
import os

from datetime import datetime
from datetime import timedelta
from google.appengine.api.urlfetch import fetch
from google.appengine.ext import db
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.api import images
from google.appengine.api.labs import taskqueue

class Site(db.Model):
  date = db.DateTimeProperty(auto_now_add=True)
  name = db.StringProperty()
  url  = db.URLProperty()

class Log(db.Model):
  date = db.DateTimeProperty(auto_now_add=True)
  response = db.FloatProperty()
  url = db.URLProperty()

class MainPage(webapp.RequestHandler):
  def get(self):
    session = appengine_utilities.sessions.Session()
    if "admin" in session:
      self.response.out.write('<html><head><title>Response Monitor!!</title></head><body>')
      self.response.out.write('<form method="post" action="/"><input type="submit" value="logout"/></form>')
      self.response.out.write('[ <a href="http://blog.elearning.co.jp?s=add&amp;search_404=1">+</a> ]<br />')
      sites = db.GqlQuery("SELECT * FROM Site ORDER BY date DESC LIMIT 10")
      for site in sites:
        self.response.out.write('[ <a href="#" onclick="if(confirm(\'Are you sure to delete it?\'))location.href=\'/delete?id=%s\'">delete</a> ] ' % (site.key()))
        self.response.out.write('<a href="/detail/%s">%s</a> [ %s ]<br />' % (site.url, site.name, site.url))
      self.response.out.write("</body></html>")
    else:
      self.response.out.write('<html><head><title>Response Monitor!!</title></head><body>')
      self.response.out.write('<form method="post" action="/"><input type="password" name="password"/><input type="submit" value="login"/></form>')
      sites = db.GqlQuery("SELECT * FROM Site ORDER BY date DESC LIMIT 10")
      for site in sites:
        self.response.out.write('<a href="/detail/%s">%s</a> [ %s ]<br />' % (site.url, site.name, site.url))
      self.response.out.write("</body></html>")
     
  def post(self):
    session = appengine_utilities.sessions.Session()
    if self.request.get("password") == "hakodate2010":
      session["admin"] = 1
    else:
      if "admin" in session:
        del session["admin"]
    self.redirect('/')

class Detail(webapp.RequestHandler):
  def get(self, key):
    self.response.out.write('<html><head><title>Log for %s</title></head><body>' % urllib.unquote(key))
    self.response.out.write("Log for '%s'" % urllib.unquote(key))
    self.response.out.write("<br />")
    responseMap = {};
    for i in range(0,4):
      query = db.GqlQuery("SELECT * FROM Log WHERE url = :1 ORDER BY date DESC LIMIT %s, 1000" % (1000 * i) , urllib.unquote(key) );
      for log in query:
        log.date+=timedelta(hours=9)
        day = str(log.date)[0:10]
        hour = str(log.date)[11:13]
        if day not in responseMap:
          responseMap[day] = {}
        if hour not in responseMap[day]:
          responseMap[day][hour] = {}
        responseMap[day][hour][log.date] = log.response

    self.response.out.write('<table border=1 style="border-style:solid ">')
    self.response.out.write("<tr><td></td>")
    for day in sorted(list(responseMap)):
      self.response.out.write("<td>")
      self.response.out.write(day)
    self.response.out.write("</td></tr>")

    for hour in range(0,24):
      hour = "%02d" % hour
      self.response.out.write("<tr>")
      self.response.out.write("<td>%s</td>" % hour)
      for day in sorted(list(responseMap)):
        self.response.out.write('<td valign="top">')
        if hour in responseMap[day]:
          limitter = 12
          for log in sorted(list(responseMap[day][hour])):
            limitter = limitter - 1
            if limitter < 0: break
            if responseMap[day][hour][log] == 0:
              self.response.out.write("<font color=red>")
              self.response.out.write(str(log)[14:19] + " ERR ")
            elif responseMap[day][hour][log] < 1.5 and responseMap[day][hour][log] != 0:
              self.response.out.write("<font>")
              self.response.out.write(str(log)[14:19] + " %.3f" % responseMap[day][hour][log])
            elif responseMap[day][hour][log] < 3:
              self.response.out.write('<font color="#FF8135">')
              self.response.out.write(str(log)[14:19] + " %.3f" % responseMap[day][hour][log])
            else:
              self.response.out.write('<font color="#FFBF00">')
              self.response.out.write(str(log)[14:19] + " %.3f" % responseMap[day][hour][log])
            self.response.out.write("</font><br />")
        self.response.out.write("</td>")
      self.response.out.write("</tr>")
    self.response.out.write("</table>")
    self.response.out.write('<div><a href="/">back</a></div>')
    self.response.out.write('</body></html>')

class Cron(webapp.RequestHandler):
  def get(self):
    size = 5
    sites = db.GqlQuery("SELECT * FROM Site ORDER BY date DESC")
    for i in range(0, sites.count(), size):
      params = {}
      for j in range(0, size):
        if i + j >= sites.count():
          break
        params["url"+str(j)] = sites[i+j].url
      taskqueue.add(url='/worker', params = params)

class Worker(webapp.RequestHandler):
  def post(self):
    if(int(self.request.headers.environ['HTTP_X_APPENGINE_TASKRETRYCOUNT']) > 0):
      return
    for key in self.request.arguments():
      try:
        url = self.request.get(key)
        fetchAndLog(url)
      except:
        pass
 
class Add(webapp.RequestHandler):
  def get(self):
    self.response.out.write("""
      <form action="/add" method="post" enctype='multipart/form-data'>
        <div>Name: <input type="text" name="name" /></div>
        <div>URL: <input type="text" name="url" /></div>
        <div><input type="submit" value="submit"/></div>
      </form>"""
)
  def post(self):
    if self.request.get('name') != '' or self.request.get('url') != '':
      site = Site()
      site.url = self.request.get('url')
      site.name = self.request.get('name')
      site.put();
    self.redirect('/')

class Delete(webapp.RequestHandler):
  def get(self):
    Site.get(self.request.get('id')).delete()
    self.redirect('/')

def fetchAndLog(url):
  log = Log()
  log.url = url
  log.response = 0.0
  log.put()
  start = datetime.now()
  ret = fetch(url = url, deadline = 30)
  end = datetime.now()
  diff = end - start
  log.response = diff.seconds + diff.microseconds / 1000000.0
  log.put()

application = webapp.WSGIApplication([
  ('/', MainPage),
  ('/add', Add),
  ('/delete', Delete),
  ('/cron', Cron),
  ('/worker', Worker),
  ('/detail/(.*)', Detail),
], debug=True)

def main():
  wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
  main()

2. ZIPのアップローダー

GAEって静的ファイルおけないんでしょ?ってよく言われるので、アップロードしたZIPファイルを展開して公開するサービスを作ってみた。ファイルはデータストアに保存しているので厳密な意味では静的ファイルではない。(静的ファイルをデプロイすることもできますが、その場合、GAEの管理ツールを使わないとファイルを更新できません。)

zipアップローダー

zipアップローダー

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#!/usr/bin/env python

import cgi
import datetime
import wsgiref.handlers

from google.appengine.ext import db
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.api import images

class Entry(db.Model):
    content = db.StringProperty(multiline=True)
    date = db.DateTimeProperty(auto_now_add=True)
    data = db.BlobProperty()
    contentType = db.StringProperty()

class MainPage(webapp.RequestHandler):
    def get(self):
        self.response.out.write('<html><body>[ <a href="http://blog.elearning.co.jp?s=add&amp;search_404=1">+</a> ]<br />')
        entries = db.GqlQuery("SELECT * FROM Entry ORDER BY date DESC LIMIT 10")
        for entry in entries:
            self.response.out.write('[<a href="#" onclick="if(confirm(\'Are you sure to delete it?\'))location.href=\'/delete?id=%s\'">d</a>][<a href="http://blog.elearning.co.jp?s=edit?id=%s&amp;search_404=1">e</a>] %s<br />' % (entry.key(), entry.key(), cgi.escape(entry.content)))
            if entry.data:
                self.response.out.write('<a href="/image/%s/data"><img src="/image/%s/thumnail"/></a><br />' % (entry.key(),entry.key()))
        self.response.out.write("</body></html>")

class Get(webapp.RequestHandler):
    def get(self, key):
        image = db.get(key)
        if type == 'thumnail':
            self.response.headers['Content-Type'] = 'image/jpeg'
            self.response.out.write(image.thumnail)
        else:
            if image.contentType:
                self.response.headers['Content-Type'] = image.contentType.encode('utf-8')
            else:
                self.response.headers['Content-Type'] = 'image/jpeg'
            self.response.out.write(image.data)

class Add(webapp.RequestHandler):
    def get(self):
        self.response.out.write("""
            <form action="/add" method="post" enctype='multipart/form-data'>
                <div><textarea name="content" rows="3" cols="60"></textarea></div>
                <div><input type="file" name="file"/></div>
                <div><input type="submit" value="submit"/></div>
            </form>"""
)
    def post(self):
        if self.request.get('content') != '' or self.request.get('file') != '':
            entry = Entry()
            entry.content = self.request.get('content')
            if self.request.get('file'):
                entry.data = self.request.POST.get('file').file.read()
                img = images.Image(entry.data)
                img.resize(60, 100)
                entry.contentType = self.request.body_file.vars['file'].headers['content-type']
            entry.put();
        self.redirect('/')

class Edit(webapp.RequestHandler):
    def get(self):
        post = Entry.get(self.request.get('id'))
        self.response.out.write("""
            <form action="/edit?id=%s" method="post">
                <div><textarea name="content" rows="3" cols="60">%s</textarea></div>
                <div><input type="submit" value="submit"/></div>
            </form>"""
% (self.request.get('id'), post.content))
    def post(self):
        entry = Entry.get(self.request.get('id'))
        entry.content = self.request.get('content')
        entry.put()
        self.redirect('/')

class Delete(webapp.RequestHandler):
    def get(self):
        Entry.get(self.request.get('id')).delete()
        self.redirect('/')

application = webapp.WSGIApplication([
    ('/', MainPage),
    ('/add', Add),
    ('/get/([-\w]+)', Get),
], debug=True)

def main():
    wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
    main()

3. ブログシステム

ブログシステムを作ってみた。一覧表示ページ、個別記事ページ、タグページぐらいしかないが、一般的な用途ならこれぐらいでいい気がする。RSSを配信しているので、FeedTweetなどを使ってTwttterに更新履歴を流したりもできる。Wordpressとなどと比べると機能も少ないし、デザインもテンプレート化できていないが、レスポンスはよいし、満足して使っている。

python-blog

python-blog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext import db
from google.appengine.api import users
import urllib, datetime, re
step = 10
title = "Python blog system"

class AuthHandler(webapp.RequestHandler):
  def get(self, key = ""):
    if users.get_current_user() == None:
      self.write("<a href=\"%s\">Sign in or register</a>." % users.create_login_url("/admin"))
    elif users.is_current_user_admin() != True:
      self.write('Your account %s is not admin. <a href=\"%s\">Log out</a> and log in with an admin account.' % (users.get_current_user(), users.create_logout_url("/admin")))
    else:
      if key:
        self.get2(key)
      else:
        self.get2()
  def post(self, key = ""):
    if users.is_current_user_admin():
      if key:
        self.post2(key)
      else:
        self.post2()
  def write(self, str):
    self.response.out.write(str)

class MainHandler(AuthHandler):
  def get(self, pageStr):
    try:
      page = int(pageStr)
    except ValueError:
      page = 0
    printHeader(self, title)
    self.write('<h1><a href="/">%s</a></h1>' % title)
    if users.is_current_user_admin():
      self.write('<h2>[<a href="http://blog.elearning.co.jp?s=&amp;search_404=1">New</a>] [<a href=\"%s\">Log out</a>]</h2>'  % users.create_logout_url("/"))
    entries = Entry.all().order("-datetime").fetch(step + 1, page * step)
    for entry in entries[:step]:
      printEntry(self, entry)
    if len(entries) > step:
      self.write('[ <a href="/%d"> Next %d</a> ]' % (page + 1, step))
    if page > 0:
      self.write('[ <a href="/%d"> Prev %d</a> ]' % (page - 1, step))
    printFooter(self)

class RSSHandler(AuthHandler):
  def get(self, pageStr):
    self.write(
u"""< ?xml version="1.0" encoding="utf-8"?>
<rdf:rdf xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns="http://purl.org/rss/1.0/">
  <channel rdf:about="http://python-blog-system.appspot.com/">
    <title>%(title)s</title>
    <link>http://python-blog-system.appspot.com</link>
    <description>%(title)s</description>
    <dc:language>ja</dc:language>
    <dc:creator>N/A</dc:creator>
    <dc:date>%(now)s</dc:date>
  </channel>"""
% {'now' : '2010-12-31T16:57:12+09:00', 'title' : title})
    for entry in Entry.all().order("-datetime").fetch(30):
      self.write("""  <item rdf:about="http://python-blog-system.appspot.com/entry/%(key)s">
    <title>%(title)s</title>
    <link>http://python-blog-system.appspot.com/entry/%(key)s</link>
    <description>%(body)s</description>
    <dc:date>%(datetime)s</dc:date>
  </item>
"""
% {"key" : entry.key(), "title" : h(entry.title), "body" : h(entry.body), "datetime" : entry.formattedDatetimeInJST})
    self.write("</rdf:rdf>")

class AdminHandler(AuthHandler):
  def get2(self, key):
    self.redirect("/")

class PostHandler(AuthHandler):
  def get2(self,key = ""):
    entry = Entry.get(key) if key != '' else Entry()
    title = "Edit Entry" if key!= '' else "New Entry"
    deleteButton = """<input type="button" value="delete" onclick="if(confirm('Are you sure to delete it?'))location.href='/delete/%s'"/>""" % key if key!= '' else ''
    printHeader(self, title)
    self.write("<h1>%s</h1>" % title)
    self.write(u"""<form method="post" action="/post/%(key)s">
<h2>タイトル</h2><input type="text" name="title" value="%(title)s" style="width:400px"/>
<h2>本文</h2><textarea name="body" style="width:400px;height:300px;">%(body)s</textarea>
<h2>タグ</h2><input type="text" name="tags" value="%(tags)s" style="width:400px"/>
<h2>画像 [<a href="http://blog.elearning.co.jp?s=uploader&amp;search_404=1" target="_blank">uploader</a>]</h2>
<div>%(deleteButton)s<input type="submit" value="submit"/></div></form>"""
% {"key" : key, "title" : entry.title, "body" : entry.body, "tags" : entry.tagStr(), "deleteButton":deleteButton})
    printFooter(self)
  def post2(self, key = ""):
    if self.request.get("title") != '' and self.request.get("body") != '':
      entry = Entry.get(key) if key != '' else Entry()
      entry.title = self.request.get("title")
      entry.body = self.request.get("body")
      entry.tags = []
      for tagStr in self.request.get('tags').replace(u' ',' ').replace('  ',' ').replace(',',' ').split(' '):
        tag = Tag.all().filter("tag =", tagStr).get()
        if tag == None:
          tag = Tag(tag = tagStr)
          tag.put()
        entry.tags.append(tag.key())
      entry.put()
    self.redirect('/')

class PostCommentHandler(AuthHandler):
  def post(self, key):
    if self.request.get("comment") != '':
      Comment(
        entry = Entry.get(key),
        comment = self.request.get("comment"),
        delpass = self.request.get("delpass"),
        nickname = self.request.get("nickname")
      ).put()
    self.redirect("/entry/%s" % key)

class DeleteHandler(AuthHandler):
  def get2(self, key):
    db.delete(Entry.get(key))
    self.redirect('/')

class DeleteCommentHandler(AuthHandler):
  def post(self, key):
    comment = Comment.get(key)
    entry_key = comment.entry.key()
    self.write(self.request.get("delpass"))
    self.write(comment.delpass)
    if self.request.get("delpass") == comment.delpass:
      db.delete(comment)
    self.redirect('/entry/%s' % entry_key)

class TagHandler(AuthHandler):
  def get(self, key):
    tagStr = urllib.unquote(key).decode('utf-8')
    title = "Python blog system / %s" % tagStr
    printHeader(self, title);
    tag = Tag.all().filter("tag =", tagStr).get()
    self.write('<h1><a href="/">Python blog system</a> / %s</h1>' % h(tagStr))
    if tag:
      for entry in tag.entries:
        printEntry(self, entry)
    else:
      self.write("<h2>Tag %s does not exist</h2>" % h(tagStr))
    printFooter(self)

class EntryHandler(AuthHandler):
  def get(self, key):
    entry = Entry.get(key)
    printHeader(self, "%s / %s" % (title, entry.title));
    self.write('<h1><a href="/">%s</a></h1>' % title)
    printEntry(self, entry, commentDetail = True)
    printFooter(self)

class UploaderHandler(AuthHandler):
  def get2(self, key):
    printHeader(self, "Uploader")
    self.write("<h1>Uploader</h1>")
    for image in Image.all():
      self.write('<h2><img src="/image/%(key)s"/><br /><input type="text" value="[img:%(key)s]" style="width:300px;font-size:x-small"/><input type="button" value="delete" onclick="location.href=\'/deleteImage/%(key)s\'"/></h2>' % {"key":image.key()})
    self.write(u'<h2><form action="/uploader" enctype="multipart/form-data" method="post"><input type="file" name="file"/><input type="submit" value="Upload"/></form></h2>')
    printFooter(self)
  def post2(self, key):
    if self.request.get('file'):
      self.write("hoge")
      image = Image()
      image.image = self.request.POST.get('file').file.read()
      image.contentType = self.request.body_file.vars['file'].headers['content-type']
      image.put()
    self.redirect('/uploader')

class DeleteImageHandler(AuthHandler):
  def get2(self, key):
    Image.get(key).delete()
    self.redirect('/uploader')

class ImageHandler(AuthHandler):
  def get(self, key):
    image = Image.get(key)
    self.response.headers['Content-Type'] = image.contentType.encode('utf-8')
    self.response.out.write(image.image)

def printEntry(self, entry, commentDetail = False):
  user = users.get_current_user()
  if user:
    editLink = '[<a href="/post/%s">edit</a>]' % entry.key()
  else:
    editLink = ''
  self.write('<div class="entry">\n<div class="entryHeader">\n')
  self.write('<h2 class="title"><a href="/entry/%(key)s">%(title)s</a> %(editLink)s</h2> <div class="entryDate">%(datetime)s</div>'
    % {"key" : entry.key(), "title" : h(entry.title), "editLink" : editLink, "datetime" : entry.formattedDatetimeInJST})
  self.write('</div>\n')#header
  self.write(replaceImages(linkURLs(nl2br(h(entry.body)))));
  self.write('\n<div class="entryFooter">tag:\n')
  for tag in entry.tags:
    tagObj = Tag.get(tag)
    self.write('<a href="/tag/%s"><span class="tag">%s</span></a>\n' % (urllib.quote_plus(tagObj.tag.encode('utf-8')) ,h(tagObj.tag)))
  if commentDetail:
    self.write(u'<h2>コメント</h2><div class="comments"><a name="comments">\n')
    for comment in entry.comments.order('datetime'):
      if comment.nickname == None or comment.nickname == "":
        comment.nickname = "Anonymous"
      delbutton = u"""
        <div style="float:right">
          <form method="post" name="form" action="/deleteComment/%(key)s">
          <input type="text" name="delpass" class="delcommentpassword"/>
          <input type="button" onclick="if(confirm('本当に削除しますか?'))form.submit()" value="削除" class="delcommentbutton"/>
          </form>
        </div><br clear="all"/>"""
% {"key":comment.key()}
      self.write('<h3 class="comment">%s: %s %s</h3>' % (comment.nickname, comment.comment, delbutton))
    self.write(u'<div style="width:84px;float:left;font-size:xx-small;position:relative;top:6px;">名前</div>')
    self.write(u'<div style="width:184px;float:left;font-size:xx-small;position:relative;top:6px;">コメント</div>')
    self.write(u'<div style="width:100px;float:left;font-size:xx-small;position:relative;top:6px;">削除パス</div>')
    self.write(u'<br clear="all"/>')
    self.write(u'<form action="/postComment/%s" method="post" style="padding:0">' % entry.key())
    self.write(u'<input type="text" name="nickname" style="width:80px;"/>')
    self.write(u'<input type="text" name="comment" style="width:180px;"/>')
    self.write(u'<input type="text" name="delpass" style="width:50px;"/>')
    self.write(u'<input type="submit" value="投稿" style="width:50px"/>')
    self.write("</form></a></div>\n")
  else:
    self.write(u'<a href="/entry/%s#comments">コメント(%s)</a>\n' % (entry.key(), entry.comments.count()))
  self.write("</div>\n")#footer
  self.write("</div>\n")#entry

def urlReplacer(match, limit = 45):
  return '<a href="%s" target="_blank">%s</a>' % (match.group(), match.group()[:limit] + ('...' if len(match.group()) > limit else ''))

def linkURLs(str):
  return re.sub(r'([^"]|^)(https?|ftp)(://[\w:;/.?%#&=+-]+)', urlReplacer, str)

def replaceImages(str):
  return re.sub(r'\[img:(.*)\]', r'<img src="http://blog.elearning.co.jp?s=\1&amp;search_404=1" style="max-width:400px"/>', str)

def printHeader(self, title):
  self.write("""< ?xml version="1.0" encoding="UTF-8"?>
< !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
  <link rel="alternate" type="application/rss+xml" title="RSS" href="rss"/>
  <meta http-equiv="content-script-type" content="text/javascript"/>
  <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
  <title>%s</title>
  <link rel="stylesheet" type="text/css" href="/css/style.css"/>
  <meta name = "viewport" content = "width=420"/>
  <script type="text/javascript">
  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-20245912-2']);
  _gaq.push(['_trackPageview']);
  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();
  </script>"""
% (h(title)))
  self.write("</head>\n<body>\n");

def printFooter(self):
  self.write(u'<div>developed by <a href="http://php6.jp/python/">python練習帳</a></div>\n</body>\n</html>')

def nl2br(str):
  return str.replace('\r\n','\n').replace('\n','<br />\n')

class Entry(db.Model):
  title = db.StringProperty(default = "")
  body = db.TextProperty(default = "")
  tags = db.ListProperty(db.Key)
  datetime = db.DateTimeProperty(auto_now_add = True)
  @property
  def formattedDatetimeInJST(self):
    return (self.datetime + datetime.timedelta(hours=9)).strftime("%Y-%m-%d %H:%M:%S")
  def tagStr(self):
    return " ".join([Tag.get(x).tag for x in self.tags])

class Tag(db.Model):
  tag = db.StringProperty()
  @property
  def entries(self):
    return Entry.all().filter('tags', self.key()).order('-datetime')

class Comment(db.Model):
  comment = db.TextProperty(default = "")
  entry = db.ReferenceProperty(Entry, collection_name = 'comments')
  user = db.UserProperty()
  datetime = db.DateTimeProperty(auto_now_add = True)
  delpass = db.TextProperty()
  nickname = db.TextProperty()

class Image(db.Model):
  image = db.BlobProperty()
  contentType = db.StringProperty()

def main():
  application = webapp.WSGIApplication([
    ('/tag/(.*)', TagHandler),
    ('/entry/(.*)', EntryHandler),
    ('/admin/?(.*)', AdminHandler),
    ('/postComment/?(.*)', PostCommentHandler),
    ('/post/?(.*)', PostHandler),
    ('/rss/?(.*)', RSSHandler),
    ('/deleteComment/?(.*)', DeleteCommentHandler),
    ('/deleteImage/(.*)', DeleteImageHandler),
    ('/delete/?(.*)', DeleteHandler),
    ('/uploader/?(.*)', UploaderHandler),
    ('/image/(.*)', ImageHandler),
    ('/(.*)', MainHandler)
  ], debug=True)
  util.run_wsgi_app(application)

def h(html):
  return html.replace('&','&amp;').replace('< ','&lt;').replace('>','&gt;').replace('"','&quot;')

if __name__ == '__main__':
  main()

おわりに

Pythonの良さをざっと紹介しましたが、どのように感じられましたでしょうか?C、Java、PHPなどに慣れた人にとっては異質なソースコードだと思います。でも、一度慣れてしまうと、すごく合理的で分かりやすい言語だと思います。このブログにPython関連の記事を書き続けるのは気が引けますので、興味を持たれた方はPython練習帳をご覧いただければと思います。

Facebookページもよろしくお願いします

CATEGORIES 北海道ラボby.yasu.tanaka98 Comments2011.01.21
TAGS ,  
記事の投稿者
田中 康英
eラーニング支援部です。ときにメールマガジンを書き、ときにブログを書き、ときにソフトを翻訳し、ときに問い合わせに返信し、ときにUstreamスタジオの準備をし、とそんな感じでお仕事しています。

Facebook comments:

コメントをどうぞ

Eメールアドレスは公開されません。

Trackback URL

管理者の承認後に表示します。無関係な内容や、リンクだけで意見や感想のないものは承認しません。