はてなブックマークするスクリプトをモジュール化した

pythonで作ったプログラムをモジュールにしたい

つまりperlでいうところのuseして呼び出して使えるようにしたい。その方法を調べる。

=> どうやらpythonでは特別なことは必要なくて、呼び出したいプログラムを、呼び出す側のプログラムでimportすればいいだけみたい。

はてなブックマークするスクリプトをモジュール化したい

はてぶするプログラム(hatebu_post.py)はいまのままだとコマンドラインから実行時に引数でURL,ID,PASSを渡してポストするようになってるけど、これをいろいろなプログラムから使えるようにしたいのでどうすればよいか?

=> 関数化もしくはクラス化してほかのプログラムにimportして使えるようにすればいいか

classにしてみた
import mechanize

class Hatebu:
  def __init__(self):
    self.br = mechanize.Browser()
    return self
これをインタラクティブシェルから実行してみる
>>> import hatebu_post2
import hatebu_post2
>>> hb = Hatebu
hb = Hatebu
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'Hatebu' is not defined

あ、間違えた。Hatebu()か
もう一度

>>> hb = Hatebu()
hb = Hatebu()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'Hatebu' is not defined
>>> 

あれ?importできてないのかな?
ファイルのプログラムの最後にprint追加して試してみる

もう一回import

>>> import hatebu_post2
import hatebu_post2
>>> 

でない。importできていないのかも。パスかな?読み込みたいモジュールのパスってどうやって設定するんだ?
どうやら、実行しているディレクトリはパス通ってるみたい。じゃあ、なんでだめなんだろう?emacsのpy-shellで実行してたから、実行ディレクトリが違ったのかな?
terminalからそのスクリプトのあるディレクとりにcdしてからpython実行してみる。

>>> import hatebu_post2
import! # importは成功した!
>>> h = Hatebu() # instance作るのは失敗
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'Hatebu' is not defined
>>> 

あ、もしかしてHatebuという名前でimportできてないのかな?

>>> from hatebu_post2 import Hatebu
>>> h = Hatebu() # instance作るのは失敗したが、さっきとはエラーが変わった
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() should return None
>>> 

こっちも試す

>>> ht = hatebu_post2.Hatebu()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() should return None
>>> 

やっぱり。さっきはHatebuが定義されていないというエラーだったけど、今度は__init__()はNoneを返すべきだというエラー。fromでimportしないといかん(もしくはh = hatebu_post2.Hatebu()というようにしないといかん)という仮定はあってたみたい。

で、TypeError: __init__() should return Noneの部分。
__init__()はなんもreturnしちゃだめなのかな?
やっぱりそうみたい。http://www.python.jp/doc/2.5/ref/customization.html

コンストラクタには、値を返してはならない という特殊な制限があります; 値を返すようにすると、実行時に TypeError の送出を引き起こします。

return文消してみる。

>>> import hatebu_post2
import!
>>> h = hatebu_post2.Hatebu()
>>> h.login('suzuki-shin', '****')
ログイン - はてな
はてな
<hatebu_post2.Hatebu instance at 0x10107f680>
>>> h.post('http://d.hatena.ne.jp/suzuki-shin/20110214#1297693223')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "hatebu_post2.py", line 23, in post
    self.br.open(bookmark_url)
  File "build/bdist.macosx-10.6-universal/egg/mechanize/_mechanize.py", line 203, in open
  File "build/bdist.macosx-10.6-universal/egg/mechanize/_mechanize.py", line 255, in _mech_open
mechanize._response.httperror_seek_wrapper: HTTP Error 404: Not Found
>>> 

あれ?うまくいかない。別のURLでテストしてみる。

>>> h.post('http://google.com')
http://google.com
http://b.hatena.ne.jp/entry/google.com
はてなブックマーク - dummy
http://b.hatena.ne.jp/entry/google.com
はてなブックマーク - ブックマーク - ブックマークを追加 - ブックマークの確認
<hatebu_post2.Hatebu instance at 0x10107f6c8>
>>> 

できた。さっきのは何が悪かったんだ?

プログラムから呼んで使ってみた

hatebu.py

import mechanize

class Hatebu:
  def __init__(self):
    self.br = mechanize.Browser()
    print "new"

  def login(self, name, password):
    self.br.open('http://www.hatena.ne.jp/login')
    print self.br.title()
    self.br.select_form(nr=0)
    self.br['name'] = name
    self.br['password'] = password
    res = self.br.submit()
    print self.br.title()
    return self.br

  def post(self, entry_url):
    print entry_url
    bookmark_url = 'http://b.hatena.ne.jp/entry/' + entry_url.replace('http://', '')
    print bookmark_url
    self.br.open(bookmark_url)
    print self.br.title()
    print self.br.geturl()
    self.br.select_form(nr=1)
    res = self.br.submit()
    self.br.follow_link(url_regex=r'add\.confirm') # 確認ページへのリンクをたどる
    print self.br.title() # タイトルを確認
    self.br.select_form(nr=1) # 2つめのフォームを指定する
    res = self.br.submit() # 投稿
    return self.br

print "import!"

hatebu_post.py

from hatebu import Hatebu

h = Hatebu()
h.login('suzuki-shin', '****')
h.post('http://coreblog.org/ats/well-used-python-modules')

実行結果

import!
new
ログイン - はてな
はてな
http://coreblog.org/ats/well-used-python-modules
http://b.hatena.ne.jp/entry/coreblog.org/ats/well-used-python-modules
はてなブックマーク - Pythonの定番モジュール集 ― TRIVIAL TECHNOLOGIES 2.0
http://b.hatena.ne.jp/entry/coreblog.org/ats/well-used-python-modules
はてなブックマーク - ブックマーク - ブックマークを追加 - ブックマークの確認

ブックマークもちゃんとできてた。

まとめ

  • はてぶするスクリプトがモジュールにできた。
  • pythonでモジュールかするには特別なことは必要ない。
  • pythonのclassのコンストラクタは値を返してはいけない。
  • import文でimportしただけだと、モジュール名.クラス名としないとクラスにアクセスできない。クラス名だけでアクセスするためにはfrom モジュール名 import クラス名とすればよい。

次はメールを受けたらそのメールの本文中のURLをはてぶにポストするようなアプリを作る予定。

普通にメールではてぶにポストできる機能があった。

Google App Engineではメール受信をトリガーにプログラムを起動できるらしい。
http://code.google.com/intl/en/appengine/docs/python/mail/receivingmail.html
ということで、それを試してみる。最終的にはメールではてぶにポストできるようにしたい。



と思ったけど、普通にメールではてぶにポストできる機能があった。
http://hatena.g.hatena.ne.jp/hatenabookmark/20091224/1261625338

がーん