482

PEP 8は次のように述べています。

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

ただし、インポートしているクラス/メソッド/関数がまれにしか使用されない場合、必要なときにインポートを行う方が効率的ですか?

これじゃないですか:

class SomeClass(object):

    def not_often_called(self)
        from datetime import datetime
        self.datetime = datetime.now()

これよりも効率的ですか?

from datetime import datetime

class SomeClass(object):

    def not_often_called(self)
        self.datetime = datetime.now()
4

22 に答える 22

345

モジュールのインポートは非​​常に高速ですが、即時ではありません。この意味は:

  • import をモジュールの先頭に置くことは問題ありません。これは、1 回しか支払われない些細なコストだからです。
  • インポートを関数内に配置すると、その関数の呼び出しに時間がかかります。

したがって、効率を気にする場合は、インポートを一番上に置きます。プロファイリングが役立つことを示す場合にのみ、それらを関数に移動します (パフォーマンスを改善するのに最適な場所を確認するためにプロファイリングを行いましたよね??)


遅延インポートを実行するのに私が見た最も良い理由は次のとおりです。

  • オプションのライブラリ サポート。異なるライブラリを使用する複数のパスがコードにある場合、オプションのライブラリがインストールされていなくても中断しないでください。
  • __init__.pyインポートされている可能性がありますが、実際には使用されていないプラグインの. 例としては、bzrlibの遅延読み込みフレームワークを使用する Bazaar プラグインがあります。
于 2008-09-24T17:38:00.760 に答える
93

import ステートメントを関数内に配置すると、循環依存を防ぐことができます。たとえば、X.py と Y.py の 2 つのモジュールがあり、両方を相互にインポートする必要がある場合、一方のモジュールをインポートすると循環依存が発生し、無限ループが発生します。いずれかのモジュールで import ステートメントを移動すると、関数が呼び出されるまで他のモジュールをインポートしようとせず、そのモジュールは既にインポートされているため、無限ループは発生しません。詳細については、こちらをお読みください - effbot.org/zone/import-confusion.htm

于 2008-09-24T20:36:46.977 に答える
73

モジュールの先頭ではなく、それらを使用する関数にすべてのインポートを配置する方法を採用しました。

私が得る利点は、より確実にリファクタリングできることです。関数をあるモジュールから別のモジュールに移動すると、その関数はテストのレガシーをすべてそのままにして機能し続けることがわかります。モジュールの上部にインポートがある場合、関数を移動すると、新しいモジュールのインポートを完全かつ最小限に抑えるために多くの時間を費やすことになります。IDEをリファクタリングすると、これは無関係になる可能性があります。

他の場所で述べたように、速度のペナルティがあります。私は自分のアプリケーションでこれを測定しましたが、私の目的には重要ではないことがわかりました。

また、検索(grepなど)に頼らずに、すべてのモジュールの依存関係を事前に確認できるのも便利です。ただし、モジュールの依存関係を気にする理由は、通常、単一のモジュールだけでなく、複数のファイルで構成されるシステム全体をインストール、リファクタリング、または移動するためです。その場合は、とにかくグローバル検索を実行して、システムレベルの依存関係があることを確認します。そのため、実際のシステムの理解を助けるためのグローバルなインポートは見つかりませんでした。

私は通常、のインポートをチェックのsys中に入れてから、引数(のような)を関数に渡します。これにより、インポートされていないコンテキストで使用できます。if __name__=='__main__'sys.argv[1:]main()mainsys

于 2008-09-24T18:16:13.397 に答える
43

ほとんどの場合、これは明快で賢明な実行に役立ちますが、常にそうであるとは限りません。以下は、モジュールのインポートが他の場所に存在する可能性がある状況の例です。

まず、次の形式の単体テストを含むモジュールを作成できます。

if __name__ == '__main__':
    import foo
    aa = foo.xyz()         # initiate something for the test

次に、実行時にいくつかの異なるモジュールを条件付きでインポートする必要がある場合があります。

if [condition]:
    import foo as plugin_api
else:
    import bar as plugin_api
xx = plugin_api.Plugin()
[...]

コードの他の部分にインポートを配置する状況は他にもあるでしょう。

于 2008-09-24T17:31:08.780 に答える
15

The first variant is indeed more efficient than the second when the function is called either zero or one times. With the second and subsequent invocations, however, the "import every call" approach is actually less efficient. See this link for a lazy-loading technique that combines the best of both approaches by doing a "lazy import".

しかし、効率以外にも、どちらか一方を好む理由があります。1 つのアプローチは、このモジュールが持つ依存関係について、コードを読んでいる人にとってより明確にすることです。また、これらには非常に異なる障害特性があります。最初のモジュールは「datetime」モジュールがない場合にロード時に失敗しますが、2 番目のモジュールはメソッドが呼び出されるまで失敗しません。

追加の注意: IronPython では、コードは基本的にインポート時にコンパイルされるため、インポートは CPython よりもかなりコストがかかる可能性があります。

于 2008-09-24T17:30:04.570 に答える
9

Curt は良い点を指摘しています。2 番目のバージョンはより明確であり、後でではなくロード時に予期せず失敗します。

通常、モジュールのロードの効率については心配していません。(a) かなり高速であり、(b) ほとんどの場合、起動時にのみ発生するからです。

予期しないときに重いモジュールをロードする必要がある場合は、__import__関数を使用して動的にロードし、例外を確実にキャッチして合理的な方法で処理する方がおそらく理にかなっています。ImportError

于 2008-09-24T17:32:50.647 に答える
8

モジュールを前もってロードすることの効率についてはあまり心配しません。モジュールが占有するメモリはそれほど大きくなく (十分にモジュール化されていると仮定)、起動コストは無視できます。

ほとんどの場合、ソース ファイルの先頭にモジュールをロードします。コードを読んでいる人にとっては、どの関数またはオブジェクトがどのモジュールから来たのかを簡単に判断できます。

モジュールをコードの別の場所にインポートする正当な理由の 1 つは、モジュールがデバッグ ステートメントで使用されている場合です。

例えば:

do_something_with_x(x)

私はこれをデバッグできます:

from pprint import pprint
pprint(x)
do_something_with_x(x)

もちろん、モジュールをコードの別の場所にインポートするもう 1 つの理由は、モジュールを動的にインポートする必要がある場合です。これは、ほとんど選択肢がないためです。

モジュールを前もってロードすることの効率についてはあまり心配しません。モジュールが占有するメモリはそれほど大きくなく (十分にモジュール化されていると仮定)、起動コストは無視できます。

于 2008-09-24T17:30:34.320 に答える
6

これはトレードオフであり、プログラマーだけが決めることができます。

ケース 1 では、必要になるまで datetime モジュールをインポートしない (および必要な初期化を行う) ことで、メモリと起動時間を節約します。「呼び出されたときのみ」インポートを実行することは、「呼び出されるたびに」インポートを実行することも意味することに注意してください。したがって、最初の呼び出しの後の各呼び出しでは、インポートを実行するための追加のオーバーヘッドが引き続き発生します。

ケース 2 では、事前に datetime をインポートして、not_often_called() が呼び出されたときにより迅速に返されるようにし、呼び出しごとにインポートのオーバーヘッドが発生しないようにすることで、実行時間と待ち時間を節約します。

効率性に加えて、インポート ステートメントが前もってある場合、モジュールの依存関係を前もって確認する方が簡単です。それらをコード内に隠すと、何かが依存しているモジュールを簡単に見つけるのが難しくなる可能性があります。

個人的には、単体テストなど、テストコード以外では使用されないことがわかっているため、常にロードしたくないものを除いて、一般的に PEP に従います。

于 2008-09-24T17:31:17.093 に答える
6

これは、すべてのインポートが一番上にある例です (これを行う必要があったのはこれだけです)。Un*x と Windows の両方でサブプロセスを終了できるようにしたいと考えています。

import os
# ...
try:
    kill = os.kill  # will raise AttributeError on Windows
    from signal import SIGTERM
    def terminate(process):
        kill(process.pid, SIGTERM)
except (AttributeError, ImportError):
    try:
        from win32api import TerminateProcess  # use win32api if available
        def terminate(process):
            TerminateProcess(int(process._handle), -1)
    except ImportError:
        def terminate(process):
            raise NotImplementedError  # define a dummy function

(レビュー:ジョン・ミリキンが言ったこと。)

于 2008-09-24T17:48:02.827 に答える
6

これは他の多くの最適化と同様に、速度のために読みやすさを犠牲にします。John が述べたように、プロファイリングの宿題を終えて、これが非常に有用な変更あり、追加の速度が必要であることがわかった場合は、それを実行してください。他のすべてのインポートと一緒にメモをとっておくとよいでしょう:

from foo import bar
from baz import qux
# Note: datetime is imported in SomeClass below
于 2008-09-24T17:49:54.477 に答える
5

モジュールの初期化は、最初のインポート時に一度だけ発生します。問題のモジュールが標準ライブラリからのものである場合、プログラム内の他のモジュールからもインポートする可能性があります。datetime のように普及しているモジュールの場合、他の多数の標準ライブラリに依存している可能性もあります。モジュールの初期化がすでに行われているため、 import ステートメントのコストはほとんどかかりません。この時点で行っていることは、既存のモジュール オブジェクトをローカル スコープにバインドすることだけです。

その情報を読みやすさの議論と結び付けてください。モジュール スコープで import ステートメントを使用するのが最善であると言えます。

于 2008-09-24T17:52:54.843 に答える
4

萌えの答えと元の質問を完了するためだけに:

循環依存に対処する必要がある場合、いくつかの「トリック」を行うことができます。a.pyモジュールおよびを使用していると仮定すると、それぞれおよび bb.pyが含まれます。それで:x()y()

  1. from importsモジュールの下部にある の1 つを移動できます。
  2. 実際にインポートが必要な関数またはメソッド内の1 つを移動できfrom importsます (複数の場所から使用する可能性があるため、これは常に可能であるとは限りません)。
  3. 2 つのうちの 1 つfrom importsを次のようなインポートに変更できます。import a

では、結論として。循環依存関係を扱っておらず、それらを回避するために何らかのトリックを行っていない場合は、この質問に対する他の回答で既に説明されている理由により、すべてのインポートを一番上に置くことをお勧めします。そして、この「トリック」を行うときは、コメントを含めてください。いつでも大歓迎です! :)

于 2013-07-01T14:41:01.067 に答える
0

@John Millikin と @VK が言及したものと非常によく似た、私のユースケースについて言及したいと思います。

オプションのインポート

Jupyter Notebook でデータ分析を行っており、すべての分析のテンプレートとして同じ IPython ノートブックを使用しています。場合によっては、モデルをすばやく実行するために Tensorflow をインポートする必要がありますが、tensorflow が設定されていない場所やインポートが遅い場所で作業することもあります。そのような場合、Tensorflow に依存する操作をヘルパー関数にカプセル化し、その関数内に tensorflow をインポートして、ボタンにバインドします。

このようにして、インポートを待たずに、または失敗したときに残りのセルを再開する必要なく、「再起動してすべて実行」を実行できました。

于 2018-07-05T19:37:34.383 に答える