web-dev-qa-db-ja.com

Pythonのローカルインポートステートメント

Importステートメントを、それを使用するフラグメントの近くに配置すると、依存関係がより明確になり、読みやすくなります。 Pythonこれをキャッシュしますか?気にする必要がありますか?これは悪い考えですか?

def Process():
    import StringIO
    file_handle=StringIO.StringIO('hello world')
    #do more stuff

for i in xrange(10): Process()

もう少し正当化:ライブラリの難解なビットを使用するメソッド用ですが、メソッドを別のファイルにリファクタリングするとき、ランタイムエラーが発生するまで外部依存関係を見逃していたことに気づきません。

45
Dustin Getz

他の答えは、importが実際にどのように機能するかについて、穏やかな混乱を引き起こします。

この文:

_import foo
_

これはおおよそ次のステートメントと同等です。

_foo = __import__('foo', globals(), locals(), [], -1)
_

つまり、要求されたモジュールと同じ名前で現在のスコープ内に変数を作成し、そのモジュール名とデフォルト引数のボートロードで__import__()を呼び出した結果をそれに割り当てます。

__import__()関数ハンドルは、概念的に文字列(_'foo'_)をモジュールオブジェクトに変換します。モジュールは_sys.modules_にキャッシュされます。これが__import__()が最初に見える場所です。sys.modulesに_'foo'_のエントリがある場合、__import__('foo')はこれを返します、それが何であれ。それは本当にタイプを気にしません。これを実際に見ることができます。次のコードを実行してみてください:

_import sys
sys.modules['boop'] = (1, 2, 3)
import boop
print boop
_

とりあえず文体的な問題は別として、関数内にimportステートメントを含めると、思い通りに機能します。モジュールがインポートされたことがない場合は、インポートされ、sys.modulesにキャッシュされます。次に、その名前のローカル変数にモジュールを割り当てます。 not not notはモジュールレベルの状態を変更します。それはdoesおそらくグローバル状態を変更します(sys.modulesに新しいエントリを追加します)。

とはいえ、関数内でimportを使用することはほとんどありません。モジュールをインポートすると、プログラムで顕著な低速化が発生する(静的な初期化で長い計算が実行される、または単に大規模なモジュールである)場合、プログラムが実際に何かのためにモジュールを必要とすることはめったになく、内部にのみインポートすることで問題ありません。それが使用される関数。 (これが気に入らない場合、Guidoは彼のタイムマシンにジャンプしてPythonを変更して、それができないようにします。)しかし、原則として、私と一般のPythonコミュニティは、モジュールスコープのモジュールの先頭にあるステートメントをインポートします。

72
Larry Hastings

参照してください PEP 8

インポートは常にファイルの先頭、モジュールのコメントとドキュメント文字列の直後、モジュールのグローバルと定数の前に置かれます。

Pythonはすべてのimportステートメントをソースファイルで宣言されている場所に関係なく同じように扱うので、これは純粋にスタイル上の選択であることに注意してください。それでも以下に従うことをお勧めしますこれにより、コードが他の人にとって読みやすくなります。

13
Andrew Hare

スタイルはさておき、インポートされたモジュールは一度だけインポートされることは事実です(そのモジュールでreloadが呼び出されない限り)。ただし、import Fooを呼び出すたびに、そのモジュールがすでにロードされているかどうかが暗黙的にチェックされます( sys.modules をチェックすることにより)。

一方がモジュールをインポートしようとし、もう一方がインポートしない2つの同等の関数の「逆アセンブリ」も検討してください。

>>> def Foo():
...     import random
...     return random.randint(1,100)
... 
>>> dis.dis(Foo)
  2           0 LOAD_CONST               1 (-1)
              3 LOAD_CONST               0 (None)
              6 IMPORT_NAME              0 (random)
              9 STORE_FAST               0 (random)

  3          12 LOAD_FAST                0 (random)
             15 LOAD_ATTR                1 (randint)
             18 LOAD_CONST               2 (1)
             21 LOAD_CONST               3 (100)
             24 CALL_FUNCTION            2
             27 RETURN_VALUE        
>>> def Bar():
...     return random.randint(1,100)
... 
>>> dis.dis(Bar)
  2           0 LOAD_GLOBAL              0 (random)
              3 LOAD_ATTR                1 (randint)
              6 LOAD_CONST               1 (1)
              9 LOAD_CONST               2 (100)
             12 CALL_FUNCTION            2
             15 RETURN_VALUE        

仮想マシン用にバイトコードがどれだけ変換されるかはわかりませんが、これがプログラムの重要な内部ループである場合は、BarアプローチよりもFooアプローチに重点を置くことをお勧めします。

timeitを使用すると、迅速かつダーティなBarテストで速度がわずかに向上することがわかります。

$ python -m timeit -s "from a import Foo,Bar" -n 200000 "Foo()"
200000 loops, best of 3: 10.3 usec per loop
$ python -m timeit -s "from a import Foo,Bar" -n 200000 "Bar()"
200000 loops, best of 3: 6.45 usec per loop
10
Mark Rushakoff

私はこれを実行しましたが、実行しないことを望みました。通常、関数を作成していて、その関数でStringIOを使用する必要がある場合は、モジュールの上部を見て、インポートされているかどうかを確認し、インポートされていない場合は追加します。

私がこれを行わないとします。関数内でローカルに追加するとします。そして、ある時点で、私または他の誰かが、StringIOを使用する他の関数をたくさん追加するとします。その人はモジュールの上部を見て、import StringIOを追加します。これで、関数には予期しないだけでなく冗長なコードが含まれています。

また、これは、私が考える非常に重要な原則に違反します。関数内からモジュールレベルの状態を直接変更しないでください。

編集:

実際、上記のすべてがナンセンスであることがわかります。

モジュールのインポートはモジュールレベルの状態を変更しません(他に何もない場合、インポートされるモジュールを初期化しますが、それはまったく同じではありません)事)。他の場所に既にインポートしたモジュールをインポートする場合、sys.modulesを検索してローカルスコープに変数を作成する以外に費用はかかりません。

これを知って、私はそれを修正した私のコード内のすべての場所を修正するのはちょっと馬鹿げているように感じますが、それは私の十字架です。

8
Robert Rossney

Pythonインタプリタがインポート文にヒットすると、インポートされているファイル内のすべての関数定義の読み取りが開始されます。これが、インポートに時間がかかる場合がある理由を説明しています。

開始時にすべてのインポートを実行する背後にあるアイデアIS Andrew Hareが指摘するように、スタイル規則です。ただし、そうすることで、次の場合にインタープリターチェックを暗黙的に行うことに注意してください。このファイルは、最初にインポートした後ですでにインポートされています。コードファイルが大きくなり、コードを「アップグレード」して特定の依存関係を削除または置換する場合にも問題になります。これには、コード全体を検索する必要がありますこのモジュールをインポートしたすべての場所を検索するファイル。

規約に従って、インポートをコードファイルの先頭に保つことをお勧めします。関数の依存関係を追跡したい場合は、その関数の docstring に追加することをお勧めします。

3
inspectorG4dget