新しいパターンを発見しました。このパターンはよく知られていますか、それともどのような意見ですか?
基本的に、ソースファイルを上下にスクラブして、どのモジュールのインポートが利用可能であるかなどを把握するのに苦労しています。今では、
import foo
from bar.baz import quux
def myFunction():
foo.this.that(quux)
次のように、すべてのインポートを実際に使用される関数に移動します。
def myFunction():
import foo
from bar.baz import quux
foo.this.that(quux)
これはいくつかのことを行います。まず、他のモジュールの内容でモジュールを誤って汚染することはめったにありません。 __all__
モジュールの変数ですが、モジュールの進化に合わせて更新する必要があります。これは、モジュールに実際に存在するコードの名前空間の汚染には役立ちません。
第二に、モジュールの一番上に大量のインポートが存在することはめったにありません。その半分以上は、リファクタリングしたために不要になりました。最後に、すべての参照された名前が関数本体内にあるため、このパターンは非常に読みやすくなっています。
この質問の(以前の) トップ投票の回答 はうまくフォーマットされていますが、パフォーマンスに関しては完全に間違っています。実演させてください
import random
def f():
L = []
for i in xrange(1000):
L.append(random.random())
for i in xrange(1000):
f()
$ time python import.py
real 0m0.721s
user 0m0.412s
sys 0m0.020s
def f():
import random
L = []
for i in xrange(1000):
L.append(random.random())
for i in xrange(1000):
f()
$ time python import2.py
real 0m0.661s
user 0m0.404s
sys 0m0.008s
ご覧のとおり、モジュールを関数にインポートする方がmore効率的です。この理由は簡単です。参照をグローバル参照からローカル参照に移動します。つまり、少なくともCPythonの場合、コンパイラーはLOAD_FAST
命令ではなくLOAD_GLOBAL
命令を発行します。名前が示すように、これらはより高速です。他の回答者は、ループの1回の反復ごとにimportによってsys.modules
を調べるパフォーマンスヒットを作為的に増大させました。
原則として、最初からインポートするのが最善ですが、モジュールに何度もアクセスする場合は、パフォーマンスがではないのが理由です。その理由は、モジュールが依存するものをより簡単に追跡できることと、そうすることは、Pythonユニバースの残りのほとんどと一貫していることです。
これにはいくつかの欠点があります。
実行時の変更を通じてモジュールをテストしたい場合は、さらに難しくなる可能性があります。する代わりに
import mymodule
mymodule.othermodule = module_stub
あなたがしなければならないでしょう
import othermodule
othermodule.foo = foo_stub
つまり、mymoduleの参照が指すものを変更するだけではなく、othermoduleにグローバルにパッチを適用する必要があります。
これにより、モジュールがどのモジュールに依存しているかがわかりにくくなります。これは、多くのサードパーティライブラリを使用している場合や、コードを再編成している場合に特に不快です。
どこでもインラインでインポートを使用するいくつかのレガシーコードを維持する必要があり、コードのリファクタリングやリパッケージが非常に困難になりました。
pythonがモジュールをキャッシュする方法のため、パフォーマンスに影響はありません。実際、モジュールはローカルの名前空間にあるため、関数にモジュールをインポートする方がパフォーマンスにわずかな利点があります。
import random
def f():
L = []
for i in xrange(1000):
L.append(random.random())
for i in xrange(10000):
f()
$ time python test.py
real 0m1.569s
user 0m1.560s
sys 0m0.010s
def f():
import random
L = []
for i in xrange(1000):
L.append(random.random())
for i in xrange(10000):
f()
$ time python test2.py
real 0m1.385s
user 0m1.380s
sys 0m0.000s
このアプローチのいくつかの問題:
py2exe
、py2app
などの依存関係を分析する必要があるプログラムを混乱させます。したがって...推奨される方法は、すべてのインポートをファイルの先頭に配置することです。インポートの追跡が困難になる場合、通常はコードが多すぎて2つ以上のファイルに分割した方がよいことを発見しました。
私がhaveであるいくつかの状況では、関数内のインポートが有用であることがわかりました。
また、各関数内にインポートを配置すると、実際にはnotがファイルの先頭よりもかなり遅くなります。各モジュールが初めてロードされるとき、それはsys.modules
に入れられ、その後の各インポートには、モジュールを検索する時間だけがかかります。これはかなり高速です(再ロードされません)。
注意すべきもう1つの便利な点は、from module import *
関数内の構文は、Python 3.0で削除されました。
ここの「削除された構文」の下にそれについての簡単な言及があります:
from foo import bar
のインポートを回避することをお勧めします。私はそれらをパッケージ内でのみ使用します。モジュールへの分割は実装の詳細であり、いずれにせよそれらの多くはありません。
パッケージをインポートする他のすべての場所では、import foo
を使用し、フルネームfoo.bar
で参照します。このようにして、特定の要素がどこから来ているかを常に知ることができ、インポートされた要素のリストを維持する必要はありません(実際、これは常に古くなり、使用されなくなった要素をインポートします)。
foo
が本当に長い名前である場合は、import foo as f
で簡略化してから、f.bar
と記述できます。これは、すべてのfrom
インポートを維持するよりもはるかに便利で明示的です。
インラインインポートを避ける理由はよく説明されていますが、最初に必要な理由に対処するための代替ワークフローではありません。
ソースファイルを上下にスクラブして、どのモジュールのインポートが利用可能かなどを把握するのに苦労しています
未使用のインポートを確認するには、 pylint を使用します。これは、Pythonコードの静的(ish)分析を行い、チェックする(多くの)ことの1つは未使用のインポートです。たとえば、次のスクリプトです。
import urllib
import urllib2
urllib.urlopen("http://stackoverflow.com")
..次のメッセージを生成します:
example.py:2 [W0611] Unused import urllib2
利用可能なインポートのチェックに関しては、私は通常TextMateの(かなり単純化された)補完に依存しています。Escを押すと、現在のWordがドキュメント内の他の単語と共に補完されます。私がやった場合import urllib
、urll[Esc]
はurllib
に展開されます。そうでない場合は、ファイルの先頭にジャンプしてインポートを追加します。
python wikiのImport ステートメントオーバーヘッド を見てください。つまり、モジュールが既にロードされている場合(sys.modules
)コードの実行速度が遅くなります。モジュールがまだ読み込まれておらず、foo
が必要なときにのみ読み込まれる場合(これは0回になる場合があります)、全体的なパフォーマンスが向上します。
パフォーマンスの観点からは、これを見ることができます: Python importステートメントは常にモジュールの先頭にあるべきですか?
一般的に、依存関係の循環を解消するために、ローカルインポートのみを使用します。
どちらのバリアントにも用途があります。ただし、ほとんどの場合、関数の内部ではなく、関数の外部にインポートすることをお勧めします。
それはいくつかの回答で言及されていますが、私の意見では、それらはすべて完全な議論に欠けています。
モジュールがpythonインタープリターに初めてインポートされるときは、トップレベルにあるか、関数内にあるかに関係なく、低速になります。 Python(私はCPythonに焦点を合わせています。他のPythonの実装では異なる場合があります)が複数のステップを実行するため、処理が遅くなります。
__pycache__
ディレクトリまたは.pyx
ファイル)に変換されているかどうかをチェックし、そうでない場合はバイトコードに変換します。sys.modules
に置かれます。Pythonは単純に sys.modules
からモジュールを返すことができるため、後続のインポートでこれらすべてを実行する必要はありません。したがって、その後のインポートははるかに速くなります。
モジュール内の関数が実際にはあまり使用されない可能性がありますが、かなり時間がかかっているimport
に依存します。次に、実際にimport
を関数内に移動できます。これにより、モジュールのインポートが速くなります(長時間ロードパッケージをすぐにインポートする必要がないため)。ただし、関数が最後に使用されると、最初の呼び出しが遅くなります(モジュールをインポートする必要があるため)。すべてのユーザーを遅くするのではなく、低速ロードの依存関係に依存する関数を使用するユーザーのみを遅くするため、これは知覚されるパフォーマンスに影響を与える可能性があります。
ただし、sys.modules
の検索は無料ではありません。非常に高速ですが、無料ではありません。したがって、実際にパッケージをimport
sする関数を頻繁に呼び出すと、パフォーマンスがわずかに低下します。
import random
import itertools
def func_1():
return random.random()
def func_2():
import random
return random.random()
def loopy(func, repeats):
for _ in itertools.repeat(None, repeats):
func()
%timeit loopy(func_1, 10000)
# 1.14 ms ± 20.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit loopy(func_2, 10000)
# 2.21 ms ± 138 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
それはほぼ2倍遅いです。
aaronasterlingが回答で少し「だまされた」 であることを認識することは非常に重要です。関数でインポートを行うと、実際には関数が高速になると彼は述べた。そして、ある程度、これは真実です。これは、Pythonが名前を検索する方法が原因です。
したがって、ローカルスコープをチェックしてからグローバルスコープをチェックする代わりに、モジュールの名前がローカルスコープで使用できるため、ローカルスコープをチェックするだけで十分です。それは実際にはそれをより速くします!しかし、それは "Loop-invariant code motion" と呼ばれる手法です。これは基本的に、ループ(または繰り返し呼び出し)の前に変数に格納することにより、ループ(または繰り返し)で行われる処理のオーバーヘッドを削減することを意味します。したがって、関数でimport
ingする代わりに、変数を使用してグローバル名に割り当てることもできます。
import random
import itertools
def f1(repeats):
"Repeated global lookup"
for _ in itertools.repeat(None, repeats):
random.random()
def f2(repeats):
"Import once then repeated local lookup"
import random
for _ in itertools.repeat(None, repeats):
random.random()
def f3(repeats):
"Assign once then repeated local lookup"
local_random = random
for _ in itertools.repeat(None, repeats):
local_random.random()
%timeit f1(10000)
# 588 µs ± 3.92 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit f2(10000)
# 522 µs ± 1.95 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit f3(10000)
# 527 µs ± 4.51 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
グローバルrandom
の繰り返しルックアップを実行すると時間がかかることがはっきりとわかりますが、関数内のモジュールのインポートと関数内の変数のグローバルモジュールの割り当てに実質的に違いはありません。
これは、ループ内の関数ルックアップも回避することにより、極端にすることができます。
def f4(repeats):
from random import random
for _ in itertools.repeat(None, repeats):
random()
def f5(repeats):
r = random.random
for _ in itertools.repeat(None, repeats):
r()
%timeit f4(10000)
# 364 µs ± 9.34 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit f5(10000)
# 357 µs ± 2.73 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
繰り返しになりますが、インポートと変数の違いはほとんどありません。
モジュールレベルのインポートが実際に問題になる場合があります。たとえば、別のインストール時の依存関係を追加したくないが、モジュールが一部の追加機能に本当に役立つ場合などです。依存関係をオプションにするかどうかの決定は、ユーザーに影響を及ぼし(予期しないImportError
を取得するか、「クールな機能」を逃した場合)、すべてのパッケージをインストールするため、軽く行うべきではありません。機能はより複雑で、通常の依存関係の場合はpip
またはconda
(2つのパッケージマネージャーについて言及)はそのまま使用できますが、オプションの依存関係の場合は、ユーザーがパッケージを後で手動でインストールする必要があります(要件をカスタマイズすることを可能にするいくつかのオプションがありますが、再び「正しく」インストールする負担がユーザーにかかります)。
しかし、これは両方の方法で行うことができます:
try:
import matplotlib.pyplot as plt
except ImportError:
pass
def function_that_requires_matplotlib():
plt.plot()
または:
def function_that_requires_matplotlib():
import matplotlib.pyplot as plt
plt.plot()
これは、代替実装を提供するか、ユーザーに表示される例外(またはメッセージ)をカスタマイズすることで、よりカスタマイズできますが、これが主な要点です。
オプションの依存関係に対する代替の「ソリューション」を提供したい場合は、トップレベルのアプローチが少し良いかもしれませんが、一般的に人々は関数内インポートを使用します。主にそれはよりきれいなスタックトレースにつながり、より短いからです。
関数内インポートは、循環インポートによるImportErrorsを回避するのに非常に役立ちます。多くの場合、循環インポートは「悪い」パッケージ構造の兆候ですが、循環インポートを回避する方法がまったくない場合は、循環につながるインポートを配置することで「循環」(したがって問題)を解決します実際にそれを使用する関数。
すべてのインポートをモジュールスコープの代わりに関数に実際に配置すると、関数が同じインポートを必要とする可能性が高いため、冗長性が生じます。これにはいくつかの欠点があります:
モジュールの一番上で大量のインポートが発生することはめったにありません。その半分以上は、リファクタリングしたので不要になりました。
ほとんどのIDEには、未使用のインポートのチェッカーがすでにあるので、数回クリックするだけでそれらを削除できます。 IDEを使用しない場合でも、静的コードチェッカースクリプトを使用して、手動で修正することができます。別の回答では、パイリントについて言及しましたが、他にもあります(たとえば、パイフレーク)。
モジュールを他のモジュールの内容で誤って汚染することはめったにありません
そのため、通常、__all__
を使用したり、関数のサブモジュールを定義したり、メインのモジュールに関連するクラス/関数/ ...だけをインポートしたりします(__init__.py
など)。
また、モジュールの名前空間を過度に汚染していると思われる場合は、モジュールをサブモジュールに分割することを検討する必要がありますが、これは数十のインポートに対してのみ意味があります。
名前空間の汚染を減らしたい場合に言及すべきもう1つの(非常に重要な)ポイントは、from module import *
インポートを回避することです。ただし、多すぎる名前をインポートするfrom module import a, b, c, d, e, ...
インポートを避け、モジュールをインポートしてmodule.c
で関数にアクセスすることもできます。
最後の手段として、常にエイリアスを使用して、「import random as _random
」を使用することにより、「パブリック」インポートでネームスペースを汚染することを回避できます。これはコードを理解するのを難しくしますが、それは何が公に見えるべきか、何がそうでないべきかを非常に明確にします。これは私がお勧めするものではありません。__all__
リストを最新の状態に保つ必要があります(推奨される賢明な方法です)。
パフォーマンスへの影響は目に見えますが、ほとんどの場合、それはマイクロ最適化です。そのため、インポートを配置する場所の決定がマイクロベンチマークによって導かれないようにしてください。依存関係が最初のimport
で本当に遅い場合を除いて、機能の小さなサブセットにのみ使用されます。そうなると、ほとんどのユーザーにとって、モジュールの知覚パフォーマンスに目に見える影響を与える可能性があります。
一般に理解されているツールを使用してパブリックAPIを定義します。つまり、__all__
変数です。最新の状態に保つのは少し面倒かもしれませんが、すべての関数で古いインポートをチェックしている場合や、新しい関数を追加してその関数に関連するすべてのインポートを追加している場合です。長期的には、__all__
を更新することで、作業量を減らす必要があります。
どちらを選択してもかまいません。どちらも機能します。一人で作業している場合は、長所と短所について推論し、どれが一番良いと思うかを行うことができます。ただし、チームで作業している場合は、既知のパターン(__all__
を使用した最上位のインポート)に固執する必要があります。これにより、(おそらく)常に行っていることを実行できるようになります。
これはいくつかのケース/シナリオで推奨されるアプローチだと思います。たとえば、Google App Engineでは、大きなモジュールの遅延読み込みをお勧めします。これにより、新しいPython VMs/interpreterをインスタンス化する際のウォームアップコストが最小限に抑えられます。 Google Engineer's これを説明するプレゼンテーションですが、これはしないことを意味するので、すべてのモジュールを遅延ロードする必要があります。