web-dev-qa-db-ja.com

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

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()
355
Adam J. Forster

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

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

したがって、効率を重視する場合は、インポートを最上位に配置してください。プロファイリングがそれが役立つと示す場合にのみ、それらを関数に移動します(パフォーマンスを改善するのに最適な場所を確認するにはdid profile?)


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

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

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

73
Moe

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

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

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

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

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

55
thousandlegs

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

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

if __== '__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()
[...]

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

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

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

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

14

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

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

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

例えば:

do_something_with_x(x)

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

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

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

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

8
Jason Baker

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

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

予想外の時間にヘビー級のモジュールをロードする必要がある場合は、おそらく__import__関数を使用して動的にロードし、 承知しました ImportError例外をキャッチし、合理的な方法で処理します。

8
Dan Lenski

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

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

ケース2では、事前にdatetimeをインポートして、not_often_called()がisを呼び出したとき、および呼び出しごとのインポートのオーバーヘッド。

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

個人的に私は通常、ユニットテストのようなものを除いてPEPに従います、そして私はknowだから常にロードされたくないテストコードを除いて使用されます。

6
pjz

以下は、すべてのインポートが最上部にある例です(これが必要なのはこれだけです)。 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

(レビュー:what John Millikin said。)

6
giltay

これは他の多くの最適化と同様です-速度のために読みやすさを犠牲にします。ジョンが言ったように、プロファイリングの宿題をして、これが非常に有用な十分な変更であることがわかったらand余分な速度が必要です。おそらく、他のすべてのインポートをメモしておくといいでしょう。

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

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

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

4
Paul

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

その情報を読みやすくするための引数と組み合わせて、モジュールのスコープでimportステートメントを使用するのが最善だと思います。

4
Jeremy Brown

Moe's answer と元の質問を完了するだけです:

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

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

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

4
Caumons

予想されるものについて多くの良い説明がありますが、繰り返し投稿された負荷チェックの実際のコスト数がすでに投稿されていないことに驚きました。

一番上からインポートする場合は、何があっても負荷がかかります。それは非常に小さいですが、通常はナノ秒ではなくミリ秒単位です。

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

以下は、関数内からいくつかのものをインポートする簡単なテストの結果です。報告された時間(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
3
TextGeek

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

だから私の答えはノーです、常にあなたのモジュールの一番上にインポートを置くとは限りません。

3
V.K.

興味深いのは、シリアル化された関数コードが他のコア、たとえばipyparallelの場合のように。

2
K.-Michael Aye

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

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
HarisankarK

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

オプションのインポート

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

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

0
Cedar