[Python] moduleなど

今回のはあらかじめ、まとまらないかもという前提の下で書きますのでご了承ください。
自分でもまだ最適の解というか、どう扱ったらいいか悩んでる部分あるので。

そもそもどうやって書いたらいいかもちょっとわからないので、自分の頭の中をまとめる意味で、
今までやってた方法と、今日考えたというか思いついた方法とをつらっと書いてみます。

ちょwwwwwwお前それはあほすぎwwwwwwww
って思うところあったら突っ込んでください:P


まずモジュールというかパッケージというか、その辺に関して。
普段Maya用のツールを書く際、僕はPythonパスの通ったディレクトリの下にパッケージを作って、それを1つのツールとします。
もちろんすごく小さな簡単なスクリプトならそのままファイルぶっこんで使いますが、2ファイル以上になる時点でパッケージにしてしまいます。

そんで、大体こんな構成です。

a_python_dir/
  |- myTool/
      |- __init__.py
      |- myTool.py
      |- ui.py
      |- other.py

で、今日考えたのはこう、

a_python_dir/
  |- myTool/
      |- __init__.py
      |- ui.py
      |- other.py

まぁ違いは見て分かるとおり1つだけなんですが、__init__.pyを使わない、というポリシーを捨てました。
何故そんなポリシーを持っていたかというと、よくわかんないから、です。
__init__.pyが何をするのか、どういう名前で読まれるのかわかんなかった。

で、そこが少しクリアになったのでツールと同名の.pyを削除して、__init__.pyを使用することにしました。

何がクリアになったかというと、__init__はパッケージそのものであるということ。
というと若干語弊があるんですが、つまりこういうことです。

import myTool

ここでmyToolをモジュールとしてインポートしたわけですが、myToolとは一体どのファイルなんでしょうか。
ってここまでのフリから、これがディレクトリのことを指すわけがなくて、ここでmyToolsは__init__.pyを指します。
つまり__init__.pyにmainという関数があった場合、myTools.main()で実行できます。

僕は今まで、ツールを読み込む際に、Mayaからこんな回りくどいことを指定ました。

from myTool import myTool
myTool.main()

つまりmyTools/myTools.pyを起動用ファイルとして使用していたんですが、
これからは__init__.pyを使おうと思っていて、そうなると

import myTool
myTool.main()

と、書けるようになります。

これは小さな違いにも見えるかもしれませんが、たとえばパッケージ内でのグローバルな情報を持たせたい場合などに便利かなと思っています。
import myToolとだけしてやれば、myTool.INFOとか、そんな感じで__init__.pyにアクセスできる。

それってパッケージ内にdata.pyを作ってimport dataしてdata.INFOにするのと何が違うの?といわれたら、まぁ好みでしかないのかもしれないですし、実際にはそっちのほうが正しいんでしょうけど、なんとなく僕は__init__.pyを使う方法がしっくり来たので、
とりあえずしばらく不便を感じるまでは使ってみます。

↑ 大嘘でした。
子モジュール(この場合だとたとえばother.py)からimport myToolとかやっちゃうと、
再帰的な呼び出し(循環参照?)が発生してしまって、importが成功しません。
ので、__init__.pyには子モジュールの呼び出しを書かないか、子モジュールの中の何を呼び出すのかを明確にするか、
子モジュールからの__init__.pyのインポートはimport __init__で行うとか、運用にあたっていくつかの配慮が必要かもしれません。
これに関しても、どれがいいのかはまた未知数・・・。

というか、__init__.pyってあんまりそういう用途に使っちゃいけないものなんですかね?
便利なんで使えるなら使いたいんですけどね。

↑使って悪い、ではなくて使い方考えないとダメ、ってことですね。グゲェェェエエェエェッッ

次がモジュールに関して。
上で示したとおり、僕はツール名と同名の.pyを作成して、それを起動スクリプトにしていました。
大体こんな感じ。

import ui

def refresh():
  import ui
  import other
  reload(ui)
  reload(other)

def main(dev=False):
  if dev:
    refresh()
  ui.UI().open()

if __name__ == '__main__':
  main(True)

大体開発の最中は、以下のようにスクリプトを実行するので、こんな感じになってます。

import sys, os.path
path = 'Z:/full/path/of/myTools.py'
sys.path.insert(0, os.path.dirname(path))
execfile(path)

これはMayaのスクリプトエディタから直接実行しているコマンドです。
pathは適宜ファイルのフルパスを与えています。

で、importしちゃったやつってのは、なんとも面倒でreload()してやらんとちゃんと更新が反映されないんですよね。
今は数も少ないので全部手書きでimportしてreloadして、ってやってたんですが、まぁそれでも面倒は面倒ですよね。
ファイル増やしたらここに書くの忘れて、あーなんか動作おかしいなー、なんてなったりして。
どうにかならんかなーとずーーーっと思ってたんですが、これに関してはsys.modulesとかdir()とかx.__dict__とかを使って解決です。
まださっき見つけたばっかなので、全然最適な方法とか見つけれてないんですが、一応たとえばこんな感じです。
ためしにosモジュールでやってみます。

mod = os
for d in dir(mod):
  _mod = mod.__dict__[d]
  if '__file__' in dir(_mod):
    reload(_mod)
    print(_mod)

結果は大体こんな感じで出ますね。

<module 'UserDict' from 'C:\Program Files\Autodesk\Maya2008\bin\python25.zip\UserDict.py'>
<module 'copy_reg' from 'C:\Program Files\Autodesk\Maya2008\bin\python25.zip\copy_reg.py'>
<module 'ntpath' from 'C:\Program Files\Autodesk\Maya2008\bin\python25.zip\ntpath.py'>

これだと指定したモジュール(というかパッケージ?)の子のみがリロードされます。多分。
ん、いや、こいつがimportしてるものでファイルが存在しているものは問答無用にリロードしちゃうのか。
それはそれでOKなら、OKです。
で、サブパッケージなんかがある場合だったら、これを再帰関数とかにしてやれば、一気に全部reload!!
全部書くとかもうマジ考えらんないですからね!
ってみんな既にそういう方法とってんのかな、そうかな、もしかして。
幸い、1個だけサブパッケージを使用したツールがあるので、今度機会があれば試してみたい、です。

あと他にグローバルからガツーンとやりたいならこういう方法もあるかも。

import sys, re
for m in sys.modules:
  if re.search('modulename', m):
    if sys.modules[m]:
    reload(sys.modules[m])

モジュールの名前で判別。
果たしてどっちが良いのか、まだ実際に試してないのでわからないんですが、
sys.modulesに関しては、これ上手く使えばいろいろと便利そうだなぁと思ったりしています。

どっちがっていうかまだ他にも方法ありそうだし。

で、話が若干前後してしまうんですが、これに伴って、Mayaからの起動部分も若干変更しています。

import sys, os.path
path = r'Z:/package/path'
sys.path.insert(0, os.path.dirname(path))
import myTool
myTool.main(True)

こうすると、開発段階からちゃんとパッケージとして扱った上で作れるので、
本番とまったく同じ動作が期待できるはずです。
そしてexecfileよりは若干キレイかなぁ、、とも思ったり。
あとはrefreshを上の方法で作り直すことも可能なはず。
元々の方法だとダメかどうかは試してないので、そもそもOKだったかもしれませんが:P

とりあえず、ぐっちゃぐっちゃのままですが、僕の方でいろいろ考えていることはこのぐらいです。
またまとまったら、こんな感じでどうすか?ってのをまとめるかもしれません。っていや、どうだろ。

とりあえず、僕の中では何かが幾分かすっきりしました。

と、まとまらないので書きたいだけ書いて去ります。
何かあればコメントで突っ込みお願い致します・・・orz

「[Python] moduleなど」への5件のフィードバック

  1. reloadの再帰やりたくなりますよねー。
    なぜ標準にないんだろう?(けどもしかして何か落とし穴があるから省いてる可能性も・・・)

    __file__持ってるのってモジュールオブジェクトだけでしたっけ?
    コードの意図が明確だし、若干危なげなんで下のうちのどれかをどうぞ

    if type(mod) == types.ModuleType:
    if isinstance(mod, types.ModuleType):
    if inspect.ismodule(mod):
    if p.__class__ == types.ModuleType:    #(汚い)

    厳密には全部同じ挙動するものじゃないですが実用上はどれも同じです。
    あとモジュールの循環参照にはご注意を > 再帰関数

  2. >hohehohe2さん
    > reloadの再帰やりたくなりますよねー。
    > なぜ標準にないんだろう?(けどもしかして何か落とし穴があるから省いてる可能性も・・・)
    多分なんですけど、スタンドアロンのツール書いてる場合ってほとんどreloadを再帰的にやる必要がないんじゃないかなーと思います。
    だからなのかなー、と。
    でも巨大なものとかサーバープログラムとかは必要そうな気もするし、実際なんでなのかわかんないんですが:P

    > if type(mod) == types.ModuleType:
    > if isinstance(mod, types.ModuleType):
    > if inspect.ismodule(mod):
    > if p.__class__ == types.ModuleType:    #(汚い)
    これは!!!!!!
    めっちゃくちゃ助かります。感謝です。
    types.ModuleTypeってすればいいんですねー・・・
    type(mod) == module
    ってなんで通らんのじゃーーーと思って、ギャース!って思ってたところなので非常にピンポイントです。助かります。あざす!

    > あとモジュールの循環参照にはご注意を > 再帰関数
    あっ、今日の朝来てまずそこにハマりましたwww
    ちょっと方法考えてきます(・ω・)ノシ

  3. 今回はリロードを大体こんな感じでやってみました。

    def refresh():
      for m in sys.modules:
        if re.search(__name__, m):
          if sys.modules[m]:
            reload(sys.modules[m])
            print ‘# Reload module : %s’ % sys.modules[m]

    なんでかわかんないですが、__dict__つかったやつは最初の1回しか動かなくて、本末転倒。
    使い方が悪かったってことなのかもしれんけど、とりあえず。

  4. if re.search(__name__, m):
    if sys.modules[m]:
    これは誤動作するかも。たとえばモジュール名が”sy”だったとき、sysがマッチしてしまいます。
    if __name__ in sys.modules:
    が無難だと思います。
    あとモジュールにrefresh()ソースに埋め込こんでしまうと全てのモジュールに書かないといけないんで
    自分なら関数独立させるかなと思います。

  5. >hohehohe2さん
    > これは誤動作するかも。たとえばモジュール名が”sy”だったとき、sysがマッチしてしまいます。
    ん、たしかに。
    自分が作るツールはそこそこ長いので、基本的にはマッチしない、という前提で書いてました。
    __name__にしちゃうと、そいつだけしか見てくれないよなー、と思って。

    > あとモジュールにrefresh()ソースに埋め込こんでしまうと全てのモジュールに書かないといけないんで
    > 自分なら関数独立させるかなと思います。
    なるほど。おっしゃる通り。

    これも含め、ほんとはもうちょい共有モジュールとか作っていろいろ楽にしたいんですけど、なかなかやる気にならない・・・
    個人的な話をすれば、ドキュメント作成の面倒くささがこの状況を引き伸ばしている気がします。
    簡単にリファレンスとかが作れたら・・・ってそこはepydocとかでソースから作ってしまえばいいのかΣ

    それです。
    今のプロジェクト終わったらそれやります:)

コメントを残す

メールアドレスが公開されることはありません。