importステートメントは常にモジュールの一番上にあるべきですか?


403

PEP 08の状態:

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

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

これではない:

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()

回答:


283

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

  • インポートをモジュールの一番上に置くことは問題ありません。なぜなら、それは一度だけ支払われる簡単なコストだからです。
  • 関数内にインポートを配置すると、その関数の呼び出しに時間がかかります。

したがって、効率を重視する場合は、インポートを最上位にします。あなたのプロファイリングショーはそれが役立つだろう場合にのみ機能にそれらを移動する(あなたがやった ??最高は、右、パフォーマンスを向上させるために場所を確認するプロファイルを)


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

  • オプションのライブラリサポート。コードに異なるライブラリを使用する複数のパスがある場合、オプションのライブラリがインストールされていなくても中断しないでください。
  • __init__.py輸入しかし、実際に使用されていないかもしれないプラグイン、の。例としては、bzrlibの遅延読み込みフレームワークを使用するBazaarプラグインがあります。

17
ジョン、これは完全に理論的な質問だったので、プロファイリングするコードはありませんでした。以前は常にPEPに準拠してきましたが、今日はそれが正しいことなのかと思うようなコードを書いていました。ご協力いただきありがとうございます。
アダムJ.フォースター

43
>関数内にインポートを置くと、その関数の呼び出しに時間がかかります。実は、この費用は一度だけ支払われると思います。Pythonはインポートされたモジュールをキャッシュするので、再度インポートするのに最小限のコストしかかかりません。
meltform 2008

24
@halfhourhacks Pythonはモジュールをインポートし、再ではないだろうが、それはまだ/モジュールが存在するかどうかだけ確認するために、いくつかの手順を実行する必要があるなどsys.modules /である
ジョン・ミリキン

24
-1。関数にインポートを配置しても、必ずしも時間がかかるとは限りません。別の質問で私の回答をご覧ください。
aaronasterling '25年

4
1つの使用例は、循環インポートの回避です(通常は賢明ではありませんが、適切な場合もあります)。モジュールm1のクラスAが、クラスAの別のインスタンスを構築するモジュールm2のクラスBのメソッドを呼び出すことがあります。クラスAのインスタンスを構築するクラスBのメソッドが、インスタンスを構築する関数を実行したときにのみインポートが実行される場合、循環インポートは回避されます。
Sam Svenbjorgchristiensensen 2014

80

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


3
はい、しかし依存関係の地獄に入ることができます。
eigenein 2014年

8
2つのモジュールが相互にインポートする必要がある場合、コードに重大な問題があります。
アンナ

オブジェクト指向プログラミングはしばしば循環依存関係に私を導きます。重要なオブジェクトクラスは、いくつかのモジュールにインポートできます。このオブジェクトが独自のタスクを実行するには、これらのモジュールの1つ以上に到達する必要がある場合があります。関数argsを介してオブジェクトにデータを送信するなど、それを回避して他のモジュールにアクセスできるようにする方法があります。しかし、これを行うとOOPに対して直感に反するように感じる場合があります(外部の世界は、その機能でタスクを実行する方法を知る必要はありません)。
Robert

4
XがYを必要とし、YがXを必要とする場合、それらは同じアイデアの2つの部分である(つまり、一緒に定義する必要がある)か、抽象化が欠落しています。
GLRoman

59

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

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

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

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

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


4
多くのIDEは、必要なモジュールを最適化してファイルに自動インポートすることにより、コードのリファクタリングを容易にします。ほとんどの場合、PyCharmとEclipseは正しい判断をしてくれました。emacsやvimでも同じ動作をする方法があると思います。
brent.payne 2011

3
グローバル名前空間のifステートメント内のインポートは、引き続きグローバルインポートです。これにより、引数が出力されます(Python 3を使用)。新しい名前空間を作成するには、関数をdef main(): print(sys.argv); if True: import sys; main();ラップif __name__=='__main__'する必要があります。
Darcinon

4
これは、グローバルスコープではなく関数内でインポートする優れた理由だと私は思います。これと同じ理由で他の人がこれを行うと述べたことがないことに私はかなり驚いています。パフォーマンスと冗長性以外に、重大な欠点はありますか?
藻類

@algalの欠点は、pepコーデックスに違反しているため、多くのPythonの人々がこれを嫌うことです。チームのメンバーを説得する必要があります。パフォーマンスの低下は最小限です。時にはそれがさらに速く、参照されstackoverflow.com/a/4789963/362951
MIT

インポートを使用する場所の近くに配置すると、リファクタリングに非常に役立つことがわかりました。多くのティムを上にスクロールして戻る必要はもうありません。私はpycharmやwing ideのようなIDEを使用し、それらのリファクタリングも使用していますが、常にそれらに依存する必要はありません。この代替インポートスタイルを使用すると、関数を別のモジュールに移動するのがはるかに簡単になります。
MIT

39

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

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

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()
[...]

コードの他の部分にインポートを配置する可能性のある他の状況がおそらくあります。


14

関数がゼロ回または1回呼び出される場合、最初のバリアントは2番目のバリアントよりも実際に効率的です。ただし、2回目以降の呼び出しでは、「呼び出しごとにインポートする」アプローチは実際には効率が低くなります。このリンクを参照してください「遅延インポート」を実行することで両方のアプローチのベストを組み合わせた遅延ロード手法については、を。

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

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


1
これは、その最初のものが行うが、より良い真実ではない: wiki.python.org/moin/PythonSpeed/...
ジェイソン・ベイカー

インポートが行われないためにメソッドが呼び出されない場合は、パフォーマンスが向上します。
Curt Hagenlocher 2008

正しいですが、メソッドが複数回呼び出されるとパフォーマンスが低下します。また、モジュールをすぐにインポートしないことによるパフォーマンス上の利点は、ほとんどの場合無視できます。例外は、モジュールが非常に大きいか、これらの種類の関数がたくさんある場合です。
Jason Baker、

IronPythonの世界では、最初のインポートはCPythonよりもはるかに高価です;)。リンクの「遅延インポート」の例は、おそらく全体的に最も優れた一般的なソリューションです。
Curt Hagenlocher 2008

よろしくお願いしますが、あなたの投稿に編集しました。知っておくと便利な情報です。
Jason Baker、

9

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

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

予期しないときに重いモジュールをロードする必要がある場合は、__import__関数を使用してモジュールを動的にロードし、例外をキャッチして適切な方法で処理することをお勧めしますImportError


8

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

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

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

例えば:

do_something_with_x(x)

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

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

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

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


(私のマシンでは)モジュールあたり数十ミリ秒の起動コストについて話している。これは常に無視できるとは限りません。たとえば、ユーザーのクリックに対するWebアプリケーションの応答性に影響する場合などです。
Evgeni Sergeev

6

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

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

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

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

個人的には、単体テストなどを除いて、常にPEPを使用します。テストコード以外では使用されないことがわかっているため、常に読み込まれることは望ましくありません。


2
-1。インポートの主なオーバーヘッドは初めて発生します。モジュールsys.modulesを検索するコストは、グローバル名の代わりにローカル名を検索するだけで済むという節約によって簡単に相殺できます。
aaronasterling '25年

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

(復習:ジョン・ミリキンの発言)


6

これは他の多くの最適化と同様です-速度を上げるために読みやすさを犠牲にします。Johnが述べたように、プロファイリングの宿題を終えて、これが十分に役立つ変更あり、さらに速度が必要な場合は、それ試してください。他のすべてのインポートをメモしておくとよいでしょう。

from foo import bar
from baz import qux
# Note: datetime is imported in SomeClass below

4

モジュールの初期化は、最初のインポート時に1回だけ行われます。問題のモジュールが標準ライブラリからのものである場合は、プログラム内の他のモジュールからもインポートする可能性があります。日時と同じくらい普及しているモジュールの場合、それは他の多くの標準ライブラリーの依存関係である可能性もあります。モジュールの初期化はすでに行われているので、インポートステートメントのコストはそのとき非常にわずかです。この時点で実行しているのは、既存のモジュールオブジェクトをローカルスコープにバインドすることだけです。

その情報を読みやすさの引数と組み合わせると、モジュールスコープにインポートステートメントを配置するのが最適だと思います。


4

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

循環依存に対処する必要がある場合、いくつかの「トリック」を実行できます。モジュールa.pyで作業していて、それぞれb.pyx()とb を含んでいると仮定しますy()。次に:

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

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


4

すでに与えられた優れた回答に加えて、輸入品の配置は単なるスタイルの問題ではないことは注目に値します。モジュールには、最初にインポートまたは初期化する必要のある暗黙的な依存関係がある場合があり、トップレベルのインポートにより、必要な実行順序に違反する可能性があります。

この問題は、Apache SparkのPython APIでよく発生します。このAPIでは、pysparkパッケージまたはモジュールをインポートする前にSparkContextを初期化する必要があります。pysparkインポートは、SparkContextが使用できることが保証されているスコープに配置するのが最適です。


4

何を期待すべきかについては多くの良い説明がありますが、すでに投稿された負荷チェックの実際のコスト数を見ていないことに驚きました。

一番上にインポートすると、何があってもロードヒットします。これはかなり小さいですが、通常はミリ秒単位であり、ナノ秒単位ではありません。

あなたが機能(複数可)の中にインポートする場合は、ロードのヒットのみを取る場合するとき、これらの関数のいずれかが最初に呼び出されます。多くの人が指摘したように、それがまったく起こらない場合は、ロード時間を節約できます。ただし、関数が頻繁に呼び出される場合は、非常に小さいヒットが繰り返されます(実際に再ロードされたのではなく、ロードされいることを確認するため)。一方、@ aaronasterlingが指摘したように、関数内でインポートすると、関数がわずかに高速なローカル変数ルックアップを使用して後で名前を特定できるため、少し節約することもできます(http://stackoverflow.com/questions/477096/python- import-coding-style / 4789963#4789963)。

以下は、関数内からいくつかのことをインポートする簡単なテストの結果です。報告された時間(2.3 GHz Intel Core i7のPython 2.7.14)は次のとおりです(2回目以降の呼び出しでは一貫性があるように見えますが、理由はわかりません)。

 0 foo:   14429.0924 µs
 1 foo:      63.8962 µs
 2 foo:      10.0136 µs
 3 foo:       7.1526 µs
 4 foo:       7.8678 µs
 0 bar:       9.0599 µs
 1 bar:       6.9141 µs
 2 bar:       7.1526 µs
 3 bar:       7.8678 µs
 4 bar:       7.1526 µs

コード:

from __future__ import print_function
from time import time

def foo():
    import collections
    import re
    import string
    import math
    import subprocess
    return

def bar():
    import collections
    import re
    import string
    import math
    import subprocess
    return

t0 = time()
for i in xrange(5):
    foo()
    t1 = time()
    print("    %2d foo: %12.4f \xC2\xB5s" % (i, (t1-t0)*1E6))
    t0 = t1
for i in xrange(5):
    bar()
    t1 = time()
    print("    %2d bar: %12.4f \xC2\xB5s" % (i, (t1-t0)*1E6))
    t0 = t1

ランタイムの変化は、負荷に応じたCPU周波数のスケーリングが原因である可能性があります。CPUクロック速度を拡大するには、忙しい作業の1秒で速度テストを開始することをお勧めします。
Han-Kwang Nienhuys

3

他の人がすでに非常にうまくやっているので、私は完全な答えを提供することを望みません。関数内のモジュールをインポートするのに特に便利であると感じたときの使用例を1つ挙げておきます。私のアプリケーションは、特定の場所にプラグインとして保存されているPythonパッケージとモジュールを使用しています。アプリケーションの起動時に、アプリケーションはその場所にあるすべてのモジュールを調べてインポートし、モジュール内を調べて、プラグインのマウントポイントを見つけた場合(私の場合は、固有の特定の基本クラスのサブクラスです) ID)それを登録します。プラグインの数は多く(現在は数十個ですが、将来的には数百個)、それぞれが使用されることはほとんどありません。プラグインモジュールの上部にサードパーティのライブラリをインポートすると、アプリケーションの起動時に少しペナルティが発生しました。特に、一部のサードパーティライブラリはインポートが重いです(たとえば、plotlyのインポートは、インターネットに接続して、起動に約1秒追加されたものをダウンロードしようとします)。プラグインでインポートを最適化することにより(使用される関数でのみ呼び出す)、起動を10秒から約2秒に短縮できました。それは私のユーザーにとって大きな違いです。

したがって、私の答えは「いいえ」であり、常にインポートをモジュールの一番上に配置しないでください。


3

興味深いことに、シリアライズされた関数コードが他のコアにプッシュされている場合、たとえば、pyyparallelの場合のように、インポートが関数内にあることが必要になる可能性がある並列処理については、単一の回答では言及していません。


1

関数内で変数/ローカルスコープをインポートすると、パフォーマンスが向上する場合があります。これは、関数内でインポートされたものの使用法に依存します。何度もループしてモジュールのグローバルオブジェクトにアクセスする場合は、ローカルとしてインポートすると役立ちます。

test.py

X=10
Y=11
Z=12
def add(i):
  i = i + 10

runlocal.py

from test import add, X, Y, Z

    def callme():
      x=X
      y=Y
      z=Z
      ladd=add 
      for i  in range(100000000):
        ladd(i)
        x+y+z

    callme()

run.py

from test import add, X, Y, Z

def callme():
  for i in range(100000000):
    add(i)
    X+Y+Z

callme()

Linuxでの時間はわずかな利益を示しています

/usr/bin/time -f "\t%E real,\t%U user,\t%S sys" python run.py 
    0:17.80 real,   17.77 user, 0.01 sys
/tmp/test$ /usr/bin/time -f "\t%E real,\t%U user,\t%S sys" python runlocal.py 
    0:14.23 real,   14.22 user, 0.01 sys

本当は壁掛け時計です。ユーザーはプログラムの時間です。sysはシステムコールの時間です。

https://docs.python.org/3.5/reference/executionmodel.html#resolution-of-names


1

読みやすさ

起動時のパフォーマンスに加えて、importステートメントをローカライズするための可読性の引数があります。たとえば、私の現在の最初のpythonプロジェクトでpythonの行番号1283から1296を取得します。

listdata.append(['tk font version', font_version])
listdata.append(['Gtk version', str(Gtk.get_major_version())+"."+
                 str(Gtk.get_minor_version())+"."+
                 str(Gtk.get_micro_version())])

import xml.etree.ElementTree as ET

xmltree = ET.parse('/usr/share/gnome/gnome-version.xml')
xmlroot = xmltree.getroot()
result = []
for child in xmlroot:
    result.append(child.text)
listdata.append(['Gnome version', result[0]+"."+result[1]+"."+
                 result[2]+" "+result[3]])

importステートメントがファイルの先頭にある場合は、何を上にスクロールするかHome、を押して内容を確認する必要ETがあります。次に、コードを読み続けるために1283行に戻る必要があります。

実際、importステートメントが関数(またはクラス)の先頭にあったとしても、多くのステートメントが配置される場合でも、ページングとバックダウンが必要になります。

Gnomeのバージョン番号の表示はめったに行われないため、importファイルの先頭に不必要な起動ラグが生じます。


0

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

オプションのインポート

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

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


0

これは魅力的な議論です。他の多くの人と同じように、私はこのトピックについてさえ検討したことがありませんでした。私のライブラリの1つでDjango ORMを使用したいので、関数にインポートを用意する必要がありました。django.setup()モデルクラスをインポートする前に呼び出す必要がありました。これはファイルの先頭にあるため、IoCインジェクターの構造により、完全に非Djangoライブラリコードにドラッグされていました。

ちょっとハックdjango.setup()して、シングルトンコンストラクターと関連するインポートを各クラスメソッドの上部に配置することにしました。これは問題なく動作しましたが、インポートが上位にないため不安になりました。また、インポートの余分な時間のヒットについて心配し始めました。それから私はここに来て、みんながこれについて取っていることに大きな関心を持って読みました。

C ++の長い背景があり、現在はPython / Cythonを使用しています。これについての私の見解は、プロファイルされたボトルネックを引き起こさない限り、関数にインポートを配置しないことです。これは、必要になる直前に変数のスペースを宣言するようなものです。問題は、何千行ものコードがあり、すべてのインポートが先頭にあることです。ですから、これからやっていき、時間のあるときに奇妙なファイルをあちこちで変更するつもりです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.