Archive for 1月 16th, 2011

イテレータを実装してみた

1月 16th, 2011 by admin

Pythonのforループは、Iterableなオブジェクトに対して実行します。

1
2
for 一時変数 in イテレートできるオブジェクト:
  ループ内の処理

Iterableかどうかは、どのように判断されるのでしょうか?Javaでは、Iterableインターフェースを実装しているかどうかで判定されますが、Pythonにはこういった仕組みはありません。オブジェクトが繰り返し処理(Iterate)に対応したメソッドをもっていればそれでよいのです。こういう仕組みをダック・タイピングといいます。

要するに、Pythonで、Iterableにするためには以下の2つのメソッドが実装されていればよいのです。

  • next()
    次の要素を返す。次の要素が無ければStopIteration例外を投げる。
  • __iter__()
    自分自身を返す

カウントダウン反復子

試しに、カウントダウンを行うイテレータを定義してみた。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> class CountDownIter(object):
...   def __init__(self, start):
...     self.i = start
...   def __iter__(self):
...     return self
...   def next(self):
...     if self.i <= 0:
...       raise StopIteration
...     self.i = self.i - 1
...     return self.i
...
>>> for i in CountDownIter(10):
...   i
...
9
8
7
6
5
4
3
2
1
0

これぐらいだと、イテレータを自作するメリットはありませんね。以下のように記述したほうが分かりやすいし楽です。

1
2
3
4
5
6
7
8
9
10
11
12
>>> for i in reversed(range(10)):
...   i
...
9
()
0
>>> for i in range(10)[::-1]:
...   i
...
9
()
0

フィボナッチ数列イテレータ

指定した数までのフィボナッチ数列を返すイテレータを作ってみた。

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
>>> class FibIter(object):
...   def __init__(self, limit):
...     self.i, self.j = 0, 1
...     self.limit = limit
...   def __iter__(self):
...     return self
...   def next(self):
...     self.i, self.j = self.j, self.i + self.j
...     if self.i > self.limit:
...       raise StopIteration
...     return self.i
...
>>>
>>> for i in FibIter(10):
...   i
...
1
1
2
3
5
8
>>> [i for i in FibIter(20)]
[1, 1, 2, 3, 5, 8, 13]
>>> list(FibIter(20))
[1, 1, 2, 3, 5, 8, 13]
>>> FibIter(20)
<__main__.FibIter object at 0x0000000002291860>
>>> print FibIter(20)
<__main__.FibIter object at 0x0000000002291860>

forループの対象として指定できるだけでなく、リスト内包表記で使ったり、list関数を使ってリストに変換したりできる。ただし、実体はIteratorなので、printで出力した場合はリストとして出力されない。

まとめ

__iter__メソッドと、nextメソッドさえ実装すればイテレータになり、forループやリスト内包表記の対象として指定できる。

URL短縮サービスつくってみた

1月 16th, 2011 by admin

GAE/PythonでURL短縮サービス作ってみたので、ソースコード丸ごと公開しちゃいます。

作成したサービスはこちらから利用できます。⇒http://pyshortener.appspot.com/

作成にあたってのポイント

実装上するにあたって工夫した点を紹介します。

連番の取得

短縮URLの短縮コードって連番を16進数や64進数などにエンコードしたものですよね。ということで、短縮URLを作成するためには、連番を作る必要があります。DBとしてMySQLを使っていればauto_incrementを指定するだけで連番が設定されます。だけど、GAE/Pythonには連番を自動生成する手法が容易されていません。そこで、最大値を管理するデータモデル(テーブル)を作成し、getNextNum()で取得できるようにしました。トランザクション内で操作しているので、同じ数字が2回発行されることは無いはずです。

短縮URLは出来るだけ短く

短縮URLはルートディレクトリに対して設定しました。/以降に何も記述されていない場合はURL作成フォームを表示しますが、1文字でも記述されていれば短縮URLが設定されているものと判断します。

ソースコード

1時間で作ったので細かい部分まではつめきれてません。まずい部分があれば指摘いただければと思います。

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
#!/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
import os

class MyHandler(webapp.RequestHandler):
  def write(self, str):
    self.response.out.write(str)

class MainHandler(MyHandler):
  def get(self, pageStr):
    if pageStr == '':
      self.write(u"""
      <form method="post" action="/">
        <input type="text" name="url"/>
        <input type="submit" value="短縮"
      </form>
  """
)
    else:
      url = URL.all().filter("num = ", int(pageStr, base = 16))
      for u in url:
        self.redirect(u.url)
  def post(self, dummy):
    num = getNextNum()
    url = URL(url = self.request.get('url'), num = num)
    url.put()
    self.redirect("/thanks/%x" % num)

class ThanksHandler(MyHandler):
  def get(self, code):
    self.write('<input type="text" size="40" value="http://%(appid)s.appspot.com/%(code)s"/><a href="/">戻る</a>' % {'appid':os.environ["APPLICATION_ID"], 'code':code})

def getNextNum():
  def procedure():
    num = MAXNUM.get_by_key_name('URL')
    if num is None:
      num = MAXNUM(key_name = 'URL')
    num.max_num = num.max_num + 1
    num.put()
    return num.max_num
  return db.run_in_transaction(procedure)

class URL(db.Model):
  url = db.URLProperty()
  num = db.IntegerProperty()

class MAXNUM(db.Model):
  max_num = db.IntegerProperty(default = 0)

def main():
  application = webapp.WSGIApplication([
    ('/thanks/(.*)', ThanksHandler),
    ('/(.*)', MainHandler)
  ], debug=True)
  util.run_wsgi_app(application)

if __name__ == '__main__':
  main()

まとめ

ドメイン

独自ドメインを設定していないため、短縮URLといっておきながら、全く短くなりません。後日、独自ドメインを設定したいと思います。

デザイン

デザイン面に関しては、協力いただける方がいれば、大歓迎です。

例外処理など

明日にでも実装します。