39


11

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

def Process():インポートStringIO file_handle = StringIO.StringIO( 'hello world')#その他

xrange(10)のiの場合:Process()

もう少し正当化:ライブラリの不可解なビットを使うメソッドのためのものですが、そのメソッドを別のファイルにリファクタリングしても、実行時エラーが出るまでは外部依存関係を見逃したことに気付きません。

6 Answer


69


他の答えは、 `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にキャッシュされます。 その後、その名前でモジュールをローカル変数に割り当てます。 モジュールレベルの状態は変更されません。 それはおそらく何らかのグローバル状態を変更する(sys.modulesに新しいエントリを追加する)。

そうは言っても、関数の中で `import`を使うことはほとんどありません。 モジュールをインポートして静的初期化で長い計算を実行したり、単に大規模なモジュールを実行したりするなど、プログラムの処理速度が著しく低下し、プログラムで実際にモジュールが必要になることはめったにありません。それが使用されている関数 (これが不愉快な場合は、Guidoは自分のタイムマシンに飛び込み、それを実行できないようにPythonを変更しました。)しかし、原則として、私と一般的なPythonコミュニティはすべてのimportステートメントをモジュールスコープのモジュールの先頭に置きました。


10


PEP 8をご覧ください。

_ インポートは常にファイルの先頭、モジュールのコメントやdocstringの直後、そしてモジュールのグローバルと定数の直前に置かれます。 _

Pythonはソースファイルのどこで宣言されているかに関係なく、すべての `import`ステートメントを同じものとして扱うので、これは純粋にスタイル上の選択です。 それでも私はあなたのコードが他の人にとって読みやすくなるのであなたが一般的なやり方に従うことを勧めます。


9


スタイルは別にして、インポートされたモジュールが一度だけインポートされることは事実です( reload`がそのモジュールで呼び出されない限り)。 しかしながら、 `import Foo`を呼び出すたびに(http://docs.python.org/library/sys.html#sys.modules [ 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

バイトコードが仮想マシン用にどれだけ翻訳されるのかわからないが、これがあなたのプログラムにとって重要な内部ループであるなら、確かに Foo`アプローチよりも Bar`アプローチにいくらか重きを置きたいと思うでしょう。

速くて汚い 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


8


私はこれをしました、そして、私がしなかったことを望みました。 通常、私が関数を書いていて、その関数が `StringIO`を使う必要があるなら、私はモジュールのトップを見て、それがインポートされているかどうかを調べ、そうでなければ追加することができます。

これをしないとします。関数内でローカルに追加したとします。 それから誰かが私または他の誰かが `StringIO`を使う他の関数を追加したとしましょう。 その人はモジュールの先頭を見て `import StringIO`を追加します。 今すぐあなたの関数は予期しないだけでなく冗長なコードを含みます。

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

編集:

実際には、上記のすべてが無意味であることがわかります。

モジュールをインポートすると、モジュールレベルの状態を変更することはできません(まだインポートされていない場合は、インポートされるモジュールが初期化されます)。 すでに他の場所にインポートしたモジュールをインポートすることは、 `sys.modules`を検索してローカルスコープ内に変数を作成すること以外は何もしません。

これを知って、私はそれを修正した私のコード内のすべての場所を修正するような愚かな感じがしますが、それは耐えるべき私の十字架です。


3


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

Andrew Hare氏が指摘しているように、最初からすべてのインポートを実行することの背後にある考え方はスタイル上の慣習です。 ただし、そうすることで、最初にインポートした後にこのファイルがすでにインポートされているかどうかを暗黙的にインタプリタに確認させることに注意してください。 コードファイルが大きくなり、コードを「アップグレード」して特定の依存関係を削除または置き換えたい場合にも問題になります。 これはあなたがこのモジュールをインポートしたすべての場所を見つけるためにあなたの全体のコードファイルを検索することを要求します。

規則に従って、インポートをコードファイルの先頭に置くことをお勧めします。 あなたが本当に関数の依存関係を追跡したいのであれば、私はその関数の docstringにそれらを追加することをお勧めします。


0


ローカルにインポートする必要がある場合、2つの方法があります。

  1. テスト目的または一時的な使用のためには、何かをインポートする必要があります。その場合はインポートを使用場所に置く必要があります。

  2. 循環的な依存関係を避けるために、関数内にそれをインポートする必要があるかもしれませんが、それは他の場所で問題があることを意味します。

それ以外の場合は、効率性と一貫性のために常に最優先にしてください。