GAEのmemcacheモジュールの関数がAptanaやEclipse+PyDevで未定義とされる原因と対策

私はGAEのコードを書く際Aptana studioを使用しているのですが、正しくPathが通っている状態でもmemcacheモジュールだけコード解析でエラーが出ます。
具体的にはmemcacheモジュール内の関数"memcache.get()"などを使用するとその関数は定義されていないというエラーが出ます。

もちろん実際にはこの関数は定義されており使用可能です。
Aptanaがエラーを出していても問題なく動作するので放っておいてもいいのですがエラーがでたまま作業をするのは目障りですし、本当のエラーがわかりづらくなってしまいます。そこでmemcacheモジュールだけがエラーとなる理由を調べてみました。

結論から書きますとこれらモジュール直下の関数が、モジュール初期化時に動的に定義されるものであることが原因です。Aptanaのコード解析は静的に定義されている関数のみをチェックしているので動的に定義されるものは未定義であると判断しているわけです。

memcacheには"memcache.get()"のような関数インターフェースとは別にClientインターフェースという物があり、これはClientオブジェクトを通してmemcacheを読み書きする仕組みです。

from google.appengine.api import memcache


class DataUpdater(webapp2.RequestHandler):
    def get(self):
        cache = memcache.Client()  # Clientオブジェクトを作成
        test_data = cache.get('sample_data')

        cache.set('test', test_data)

というようにClientオブジェクトを定義してそれを用いてmemcacheにアクセスします。この方法であればAptanaのコード解析もエラーが出ませんしサジェストも動作します。

上でmemcacheモジュールは初期化される際に"memcache.get()"などの関数インターフェースを動的に定義していると書きましたが、どのようにそれを行っているかというとモジュール内で上記のClientオブジェクトの作成を行っています。

このClientオブジェクトはmemcacheモジュール内で_CLIENTという名前で定義されこのオブジェクトの".get()"や".set()"などすべてのメソッドがmemcache直下の同名関数から参照されるように動的に定義されます。
つまり"memcache.get()"は実際には"memcache._CLIENT.get()"を呼び出していて"memcache.set()"は"memcache._CLIENT.set()"を呼び出しているわけです。

これを踏まえ、import方法を工夫してAptanaがエラーをださず、サジェストやコード補完も有効になるようにしてみます。

from google.appengine.api import memcache as _memcache
memcache = _memcache._CLIENT

class DataUpdater(webapp2.RequestHandler):
    def get(self):
        test_data = memcache.get('sample_data')

        memcache.set('test', test_data)

このようにmemcacheというシンボルがmemcache._CLIENTを参照するようにimport後に定義するとそれ以降のコードでは"memcache.get()"などが使用できます。開発中はこのように書いておきデプロイ時にimportを通常のように戻してもよいですし、気持ち悪くなければこのままデプロイしても問題ないと思います。

もっと手っ取り早い対策としてAptanaを「エラーを出さないように設定する」という方法もあります。
その場合は"Preferences>PyDev>Editor>Code Analysis"から"Undefined"のタブを選んで"Undefined variable from import"の項目を"Ignore"に設定します。

ただしこの方法はあくまでエラーを出さないように設定するだけなのでサジェストやコード補完などの機能は使用できません。また本当のエラーも警告されなくなってしまいます。

まとめ

memcacheモジュールの関数がエラーになる原因はモジュール初期化時に動的に定義されているから。
対策は

  • memcache.Client()でインスタンスを作成してそれを用いる
  • import方法に細工する
  • そもそもエラーが出ないようにAptanaを設定する