2009-09-22 7 views
27

私は、モジュールレベルの変数を遅延ロードする方法を見つけようとしています。遅延モジュール変数 - これを行うことはできますか?

具体的には、私はiTunesと話すための小さなPythonライブラリを作成しました。私はDOWNLOAD_FOLDER_PATHモジュール変数を持っています。残念ながら、iTunesはダウンロードフォルダの場所を教えてくれないので、いくつかのポッドキャストトラックのファイルパスを取得し、 "Downloads"ディレクトリが見つかるまでディレクトリツリーを登る機能を書いた。

これは2〜2秒かかるので、モジュールのインポート時ではなく、遅延して評価したいと思います。

モジュール変数が最初にアクセスされたときに遅延割り当てする方法はありますか、それとも関数に依存する必要がありますか?

答えて

52

...あなたはモジュールでそれを行うことはできませんが、それは、itun.pyで、例えば、コードモジュールだった「あるかのように」あなたはクラスを偽装することができます

import sys 

class _Sneaky(object): 
    def __init__(self): 
    self.download = None 

    @property 
    def DOWNLOAD_PATH(self): 
    if not self.download: 
     self.download = heavyComputations() 
    return self.download 

    def __getattr__(self, name): 
    return globals()[name] 

# other parts of itun that you WANT to code in 
# module-ish ways 

sys.modules[__name__] = _Sneaky() 

今、誰もができるimport itun。実際にitun._Sneaky()インスタンスを取得してください。 __getattr__itun.pyの中の他のものにアクセスできるようにしました。これは、_Sneakyよりも、最上位のモジュールオブジェクトとしてコード化する方が便利かもしれません!_)

+7

それは絶対に華麗です。職場のマスター。 – wbg

0

変数がモジュールではなくクラスに存在する場合は、getattrをオーバーロードしたり、より良い方法でinitに入力したりできます。

+0

ええ、私は知っています。私はAPIのためにモジュールでそれを望んでいました。 クラスでは、私はそれを行うのに 'property'を使います。怠惰な読み込みに最適です。 – wbg

3

モジュール変数が最初にアクセスされたときに遅延割り当てする方法はありますか、それとも関数に依存する必要がありますか?

私はあなたがここであなたの問題に最適な解決策だと言っても間違いないと思います。 簡単な例を示します。

#myfile.py - an example module with some expensive module level code. 

import os 
# expensive operation to crawl up in directory structure 

高価な操作は、モジュールレベルであればインポート時に実行されます。これを止める方法はありません。モジュール全体を怠惰にインポートすることはできません!

#myfile2.py - a module with expensive code placed inside a function. 

import os 

def getdownloadsfolder(curdir=None): 
    """a function that will search upward from the user's current directory 
     to find the 'Downloads' folder.""" 
    # expensive operation now here. 

この方法を使用すると、ベストプラクティスに従います。

+0

Hmmmm。それはPythonのZenに沿って、それを行うための明白で簡単な方法ですが、私はそれをAPI的には好きではありません。 – wbg

11

Python 3.3でAlexの実装を使用しましたが、無残クラッシュ:AttributeErrorが提起されなければならないので コード

def __getattr__(self, name): 
    return globals()[name] 

は、KeyError正しくないではありません。 イントロスペクションの多くは、インポート時に が行われているので、これは__path__などの属性を探して、Pythonの3.3の直下に墜落し、__loader__など

ここでは、怠惰な輸入のためにできるように、我々のプロジェクトに今使用バージョンがある モジュールで。

""" config.py """ 
# lazy initialization of this module to avoid circular import. 
# the trick is to replace this module by an instance! 
# modelled after a post from Alex Martelli :-) 

Lazy module variables--can it be done?

class _Sneaky(object): 
    def __init__(self, name): 
     self.module = sys.modules[name] 
     sys.modules[name] = self 
     self.initializing = True 

    def __getattr__(self, name): 
     # call module.__init__ after import introspection is done 
     if self.initializing and not name[:2] == '__' == name[-2:]: 
      self.initializing = False 
      __init__(self.module) 
     return getattr(self.module, name) 

_Sneaky(__name__) 

モジュールは今のinit関数を定義する必要があります:モジュールの__init__はない特別な名前を持っている最初の属性アクセス まで遅延されます。この関数は、自分自身をインポートするかもしれないモジュールをインポートする を使用することができる。

def __init__(module): 
    ... 
    # do something that imports config.py again 
    ... 

コードが別のモジュールに入れることができ、それは特性上記例のように で拡張することができます。

多分それは誰かにとって役に立ちます。

2

最近、私は同じ問題を抱え、それを行う方法を見つけました。このLazyObject

class LazyObject(object): 
    def __init__(self): 
     self.initialized = False 
     setattr(self, 'data', None) 

    def init(self, *args): 
     #print 'initializing' 
     pass 

    def __len__(self): return len(self.data) 
    def __repr__(self): return repr(self.data) 

    def __getattribute__(self, key): 
     if object.__getattribute__(self, 'initialized') == False: 
      object.__getattribute__(self, 'init')(self) 
      setattr(self, 'initialized', True) 

     if key == 'data': 
      return object.__getattribute__(self, 'data') 
     else: 
      try: 
       return object.__getattribute__(self, 'data').__getattribute__(key) 
      except AttributeError: 
       return super(LazyObject, self).__getattribute__(key) 

、あなたはオブジェクトに対してinitメソッドを定義することができ、そしてオブジェクトが遅延初期化され、サンプルコードは、次のようになります。

o = LazyObject() 
def slow_init(self): 
    time.sleep(1) # simulate slow initialization 
    self.data = 'done' 
o.init = slow_init 

上記oオブジェクトはまったく同じになります'done'オブジェクトが持っているものは何でも方法は、たとえば、あなたが行うことができます:

# o will be initialized, then apply the `len` method 
assert len(o) == 4 

完全なテスト(2.7で動作)とのコードはここで見つけることができます:

https://gist.github.com/observerss/007fedc5b74c74f3ea08

関連する問題