みなさん、Pythonってご存知でしょうか?
ニシキヘビ。。ではないです。
プログラミング言語のPythonです。
このPython。日本国内では、それほど知名度は高くないのですが、2010年で最も成長したプログラミング言語にも選ばれるなど、急速に人気の高まりつつある言語です。2011年にはPHPとC++を抜かして3位になれそうな勢いです!では、このPython、どうして急に人気がでてきたのでしょうか?理由は大きくわけて3つあると思います。
Pythonが急に普及し始めた理由
- GAEで動作する
これが最大の理由だと思います。Googleのサーバを使って簡単にサービスを提供できます。1日あたり1GBまでの転送なら、料金は一切かかりません ※1。無料利用分を超えて使った場合も、課金設定(1日当たりの支払い額の上限)を設定するだけで、簡単にサーバを増強できます。ロードバランス、データベースのレプリケーション、そんなことは一切考える必要がありません。全部自動でやってくれます。固定費が不要なので、AmazonEC2なんかより、よっぽど敷居は低いです。
- 理解しやすさ
これには異論が多いと思います。C、Java、PHPなどとは似ても似つかないソースコード。でも、Python、慣れてしまえば読みやすいですよ。Pythonはインデントに縛られた言語です。プログラムの構造と見た目(インデント)が必ず一致しているので、誰が書いても似たようなソースコードになります。慣れないうちは思うように書き進められないかもしれませんが、しばらく使い込めば「読みやすいコード」が自然にかけるようになっているはずです。
- 遅さは問題ではなくなった
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&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の管理ツールを使わないとファイルを更新できません。)
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&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&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となどと比べると機能も少ないし、デザインもテンプレート化できていないが、レスポンスはよいし、満足して使っている。
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=&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&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&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('&','&').replace('< ','<').replace('>','>').replace('"','"') if __name__ == '__main__': main() |
おわりに
Pythonの良さをざっと紹介しましたが、どのように感じられましたでしょうか?C、Java、PHPなどに慣れた人にとっては異質なソースコードだと思います。でも、一度慣れてしまうと、すごく合理的で分かりやすい言語だと思います。このブログにPython関連の記事を書き続けるのは気が引けますので、興味を持たれた方はPython練習帳をご覧いただければと思います。