Python:お手軽にPluggableにする

Pythonで自分用の小物アプリを結構書き溜めてるんですけど、実はそのほとんどにプラグインシステムみたいなのをつくってたりします。

たとえばファイルの整理自動化みたいなの。ファイルの移動前に処理を追加する、てな部分をプラグインにしてあるわけです。

まぁ機能的にはCPANのClass::Componentっぽいもんですね。ですがそこまで高機能なのはいらないので、シンプルに自分が必要な部分だけまとめてみました。

pluggable.py

python code
  1. from inspect import getmembers, ismethod
  2. import types
  3.  
  4. class Pluggable(object):
  5.   def __init__(self):
  6.     self.hooks = {}
  7.  
  8.   def load(self, *config):
  9.     self.load_config(*config)
  10.     self.load_plugins()
  11.  
  12.   def load_config(self, *config):
  13.     self.config = []
  14.     for i in config:
  15.       if isinstance(i, ("".__class__, u"".__class__)):
  16.         self.config.append((i, {}))
  17.       else:
  18.         self.config.append((i[0], len(i) > 1 and i[1] or {}))
  19.  
  20.   def load_plugins(self):
  21.     for klass,config in self.config:
  22.       plugin = None
  23.       try:
  24.         plugin = eval(klass)
  25.       except (NameError, AttributeError),e:
  26.         names = klass.split(".")
  27.         mod = __import__(".".join(names[0:-1]))
  28.         globals()[names[0]] = mod
  29.         for name in names[1:-1]: mod = getattr(mod, name)
  30.         plugin = getattr(mod, names[-1])
  31.  
  32.       p = plugin(config)
  33.       make_closure = lambda obj,name: lambda *a, **k: getattr(obj,name)(*a, **k)
  34.  
  35.       for name, method in ((n,m) for n,m in getmembers(p) if ismethod(m)):
  36.         if hasattr(method, "__plugin_method__"):
  37.           setattr(self, name, make_closure(p, name))
  38.         for hook_type in getattr(method, "__hook_types__", []):
  39.           if hook_type not in self.hooks: self.hooks[hook_type] = []
  40.           self.hooks[hook_type].append(getattr(p, name))
  41.  
  42.   def _run_hooks(self, dct, f=lambda x:x):
  43.     result = {}
  44.     for (name,value) in dct.iteritems():
  45.       if name not in self.hooks: continue
  46.       k,a = {}, value
  47.       if isinstance(value[-1], types.DictType):
  48.         k,a = value[-1],value[0:-1]
  49.       result[name] = [m(*a, **k) for m in f(self.hooks[name])]
  50.     return result
  51.  
  52.   def run_hooks(self, dct):
  53.     return self._run_hooks(dct)
  54.  
  55.   def reverse_run_hooks(self, dct):
  56.     return self._run_hooks(dct, reversed)
  57.  
  58. class Plugin(object):
  59.   def __init__(self, config):
  60.     self.config = config
  61.  
  62. method = lambda f: setattr(f, "__plugin_method__", True) or f
  63. hook = lambda *args: lambda f: setattr(f, "__hook_types__", args) or f
  64.  

機能的には

  • クラスに対して設定とともにメソッドを追加する(setattrします)
  • 任意のhookの登録と実行

ぐらいです。

使い方は

plugins.py

python code
  1. import pluggable
  2. class People(pluggable.Pluggable):
  3.   pass
  4.  
  5. class Hacker(pluggable.Plugin):
  6.   @pluggable.method
  7.   def hack(self):
  8.     print self.config["msg"]
  9.  
  10.   @pluggable.hook("hello")
  11.   def hello(self, msg1, msg2, msg3):
  12.     print "Hello World!!", msg1, msg2, msg3
  13.  
  14. class Otaku(pluggable.Plugin):
  15.   @pluggable.method
  16.   def niconico(self, msg):
  17.     print self.config["msg"], msg
  18.  
  19.   @pluggable.hook("hello")
  20.   def hello(self, msg1, msg2, msg3):
  21.     print u"にぱー", msg1, msg2, msg3
  22.  
 code
  1.  
  2.  

みたいにプラグインクラスを作っておいて

python code
  1. p = People()
  2. p.load(
  3.   ("plugins.Hacker", {"msg": "Happy Hacking!"}),
  4.   ("plugins.Otaku", {"msg": u"ニコ厨ですが"})
  5. )
  6. p.hack() #=> Happy Hacking!
  7. p.niconico(u"なにか?") #=> ニコ厨ですが なにか?
  8. p.run_hooks({"hello":([1,2],{"msg3":3})})
  9. #=> Hello World!! 1 2 3
  10. #=> にぱー 1 2 3
  11. p.reverse_run_hooks({"hello":[1,2,3]})
  12. #=> にぱー 1 2 3
  13. #=> Hello World!! 1 2 3
  14.  

という感じで使います。あとは設定の部分をyamlなんかに押し出せばオッケーというわけ。

モジュールのインポートは自動でやってくれます。Pythonは地味に動的なimportが面倒だったので、ライブラリにまとめたことでだいぶ手元の小物アプリがすっきりしました。


  • 2008/01/27 若干修正を加えました。
07.27.08/12am

Python版Yahooテキスト解析 APIライブラリ

趣味プログラマやってるわけですが、最近はずっとC言語を書いています。やっぱCはいいですね。あと3Dモデリングに手を出し始めました。目指せ最強の器用貧乏。

というのは置いといて、Yahooのテキスト解析API出ましたね。これは便利そう。というわけで、Pythonのライブラリ置いておきます。

ダウンロード

使い方

ソースに書いてあるんですが、こんな感じです。

[python] import yahooapi.jlp as jlp client = jlp.MAServiceAPI("your_appid") result = client.parse(sentence=u"庭には二羽ニワトリがいる。", results= jlp.MA+jlp.UNIQ, filter = jlp.VERB + jlp.NOUN)

print result.ma_result.word_list.word[0].surface

=> u"庭"

print result.ma_result.word_list.word[0].reading

=> u"にわ"

まぁ以前つくったLingrのAPIライブラリとほとんど一緒です。ポイントとしてはフィルタとか品詞が定数を+-して指定できることかな。jlp.WORD_TYPE_ALL - jlp.NOUNとかして、名詞以外とってくるとかも簡単にかけます。

あと、実は前ちょっとつくってたYahooのAPIクラスをベースにしてるんで

[python] class Result(yahooapi.Result) : xml_root_name = "ResultSet"

class WebSearchServiceAPI(yahooapi.YahooAPI): service_name = "WebSearchService" result_class = Result api_name = "search"

みたいなのを作れば、検索APIとか、ほかのAPIも同様に使えたりします。

07.27.08/12am

Python版Lingr APIライブラリ

最近はPythonとは言えエミュを書いているので16進数やら、アセンブラやら、パフォーマンスやらとお友達。

たまには富豪的に組みたいなー、ということで流行のLingr APIのライブラリ。Pythonってもうあるのかなあ。PHPやらPerlは一瞬で出ててびっくりします。

そんなたいしたものじゃないし、遊びついでで結構適当なんですけど、良ければどうぞ。

ダウンロード

使い方

ソースに書いてあるとおりなんですが。

[python] lingr = Lingr("your api_key") lingr.api.session.create() lingr.api.room.enter(id="room id", nickname="nickname") lingr.api.romm.say(message="hello!") lingr.api.room.exit() lingr.api.session.destroy()

result = lingr.api.explore.search(q="scheme") print result.rooms

こんな感じです。パラメーターのうち、api_key,session,ticket(user用とroom用両方)は自動的に渡されるので書かなくて大丈夫です。

よもや話

中身はかなり富豪的。というかインターフェイス重視。こういうAPI用のライブラリってやっぱり使い勝手が最優先だと思うので。メソッド呼び出しは.(ドット)でつなげてそのまま。結果はresult["room"]でもresult.roomでもアクセス可能。

俺がPythonでWebAPIのクライアントを書くとだいたいいつもこんな感じです。

07.27.08/12am

About

Author:yuin(http://inforno.net/)

文学部文化学科卒という生粋の文系趣味プログラマ。

主にRuby、Javascript、PHP、JAVA,Python,C,Scala,Schemeなどを使っています。今はPythonな感じかもしれない。今後作曲活動なども復活するかもしれない。

Pages