関数内にインポートするのはpythonicですか?


126

PEP 8は言う:

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

職業上、私はPEP 8に違反しています。時々、関数内にデータをインポートします。原則として、単一の関数内でのみ使用されるインポートがある場合にこれを行います。

意見はありますか?

編集(関数にインポートするのが良い考えである理由):

主な理由:コードをより明確にすることができます。

  • 関数のコードを見るとき、「関数/クラスxxxとは何ですか?」(xxxは関数内で使用されています)。すべてのインポートがモジュールの上部にある場合、そこに行ってxxxが何かを判断する必要があります。これは、を使用する場合の問題from m import xxxです。m.xxx関数内を見ると、おそらくもっとわかります。何に依存しmます:よく知られているトップレベルのモジュール/パッケージ(import m)ですか?それともサブモジュール/パッケージ(from a.b.c import m)ですか?
  • 場合によっては、xxxが使用されている場所の近くに追加情報(「What is xxx?」)があると、機能が理解しやすくなります。

2
パフォーマンスのためにそれをしますか?
Macarse 2009年

4
場合によってはコードが明確になると思います。(関数が呼び出されるたびにimportステートメントが実行されるため)関数でインポートすると、パフォーマンスが低下すると思います。
codeape、2009年

「関数/クラスxxxとは」と回答できます。from xyz import abc構文ではなくimport xyz構文を使用する
Tom Leys

1
明快さが唯一の要因である場合、そのためにUは関連するコメントを含めることもできます。;)
ラクシュマンプラサード

5
@becomingGuru:もちろん、コメントは現実と同期しなくなる可能性があります...
codeape

回答:


88

長い目で見れば、ほとんどのインポートをファイルの先頭に配置することで、モジュールがどれほど複雑であるかが一目でわかるため、インポートする必要があることを理解できると思います。

既存のファイルに新しいコードを追加する場合は、通常、必要な場所でインポートを実行し、コードが残っている場合は、インポート行をファイルの先頭に移動して、永続性を高めます。

もう1つのポイントは、ImportErrorコードを実行する前に例外を取得することです。これは、健全性チェックとして行うためです。これが、最初にインポートするもう1つの理由です。

私が使用しpyChecker、未使用のモジュールをチェックします。


47

この点でPEP 8に違反する2つの状況があります。

  • 循環インポート:モジュールAはモジュールBをインポートしますが、モジュールBの何かにはモジュールAが必要です(これは多くの場合、循環依存関係を排除するためにモジュールをリファクタリングする必要がある兆候です)
  • pdbブレークポイントの挿入:import pdb; pdb.set_trace()これは便利なb / c import pdbです。デバッグしたいすべてのモジュールの先頭に置きたくないので、ブレークポイントを削除するときにインポートを削除することを覚えておくのは簡単です。

これら2つのケース以外では、すべてを上に配置することをお勧めします。依存関係がより明確になります。


7
モジュール全体の依存関係が明確になることに同意します。しかし、それによって関数レベルでコードを不明確にして、すべてを最上位にインポートすることができると私は信じています。関数のコードを見ているときに、「関数/クラスxxxとは何ですか?」(xxxは関数内で使用されます)。そして、ファイルの一番上を見て、xxx​​がどこから来たかを確認する必要があります。これは、from m import xxxを使用する場合の問題です。m.xxxを見ると、より多くのことがわかります-少なくともmが何であるか疑いがない場合。
codeape、2009年

20

使用する4つのインポートの使用例を以下に示します

  1. import(とfrom x import yimport x as y)上部の

  2. インポートの選択肢。頂点で。

    import settings
    if setting.something:
        import this as foo
    else:
        import that as foo
  3. 条件付きインポート。JSON、XMLライブラリなどで使用されます。頂点で。

    try:
        import this as foo
    except ImportError:
        import that as foo
  4. 動的インポート。これまでのところ、この例は1つだけです。

    import settings
    module_stuff = {}
    module= __import__( settings.some_module, module_stuff )
    x = module_stuff['x']

    この動的インポートはコードを取り込むのではなく、Pythonで記述された複雑なデータ構造を取り込むことに注意してください。手作業で漬け込んだことを除けば、漬け込んだデータのようなものです。

    これは、多かれ少なかれ、モジュールの上部にもあります


コードをわかりやすくするために、次のことを行います。

  • モジュールは短くしてください。

  • すべてのインポートがモジュールの上部にある場合、そこに行って名前が何であるかを判断する必要があります。モジュールが短い場合、それは簡単です。

  • 場合によっては、名前が使用されている場所の近くに追加の情報があると、機能がわかりやすくなります。モジュールが短い場合、それは簡単です。


モジュールを短くすることはもちろん非常に良い考えです。ただし、利用可能な機能の「インポート情報」を常に利用できるようにするには、モジュールの最大長を1画面にする必要があります(おそらく最大100行)。そして、それはおそらくほとんどの場合実用的であるには短すぎるでしょう。
codeape、2009年

私はこれを論理的な極限までとらえることができると思います。複雑さを管理するために特別なインポート手法を必要としない、モジュールが「十分に小さい」というバランスポイントがあると思います。私たちの平均モジュールサイズは-偶然にも-約100行です。
S.Lott、2009年

8

覚えておくべきことの1つは、不要なインポートはパフォーマンスの問題を引き起こす可能性があることです。したがって、これが頻繁に呼び出される関数である場合は、インポートを先頭に置く方がよいでしょう。もちろん、これ最適化であるため、関数内でインポートする方がファイルの先頭でインポートするよりも明確であるという正当なケースがある場合、ほとんどの場合、パフォーマンスが低下します。

IronPythonを実行している場合、関数内にインポートする方が良いと言われています(IronPythonでのコードのコンパイルは遅くなる可能性があるため)。したがって、関数内にインポートする方法を得ることができるかもしれません。しかし、それ以外は、慣習と戦うだけでは価値がないと私は主張します。

原則として、単一の関数内でのみ使用されるインポートがある場合にこれを行います。

私が付け加えたいもう一つのポイントは、これが潜在的なメンテナンス問題かもしれないということです。以前は1つの関数でのみ使用されていたモジュールを使用する関数を追加するとどうなりますか?ファイルの先頭にインポートを追加することを覚えていますか?それとも、インポートのためにすべての関数をスキャンしますか?

FWIW、関数内にインポートすることが理にかなっている場合があります。たとえば、cx_Oracleで言語を設定する場合は、インポート_する前に NLS LANG環境変数を設定する必要があります。したがって、次のようなコードが表示される場合があります。

import os

oracle = None

def InitializeOracle(lang):
    global oracle
    os.environ['NLS_LANG'] = lang
    import cx_Oracle
    oracle = cx_Oracle

2
メンテナンスの問題に同意します。コードのリファクタリングは少し問題になる場合があります。以前は1つの関数でのみ使用されていたモジュールを使用する2番目の関数を追加する場合-インポートを一番上に移動するか、2番目の関数でモジュールをインポートすることにより、独自の一般ルールを破ります。
codeape

2
パフォーマンスの議論は逆の方向にも行くことができると思います。モジュールのインポートには時間がかかる場合があります。スーパーコンピュータのような分散ファイルシステムでは、numpyのような大きなモジュールをインポートすると数秒かかる場合があります。まれにしか使用されない単一の関数にのみモジュールが必要な場合、関数にインポートすると、一般的なケースが大幅に高速化されます。
amaurea 2016年

6

セルフテストを行うモジュールについては、以前このルールに違反しました。つまり、通常はサポートに使用されるだけですが、メインを定義して、自分で実行した場合に機能をテストできるようにしています。その場合、私は時々 、インポートgetoptcmdちょうどメインで、私はそれがこれらのモジュールは、モジュールの正常な動作とは何の関係もないし、テストのためだけに含まれていることを、コードを読んで誰かに明らかにしたいので。


5

モジュールを2回ロードすることについての質問-なぜ両方ではないのですか?

スクリプトの先頭にあるインポートは依存関係を示し、関数内の別のインポートはこの関数をよりアトミックにしますが、連続的なインポートは安価であるため、パフォーマンスの低下を引き起こさないようです。


3

限り、それはだとしてimportではなくfrom x import *、あなたが一番上にそれらを置く必要があります。グローバルネームスペースに名前を1つだけ追加し、PEP 8を使用します。さらに、後で別の場所で必要になった場合でも、何も移動する必要がありません。

大した問題ではありませんが、違いはほとんどないので、PEP 8の内容を実行することをお勧めします。


3
実際、from x import *関数内に置くと、少なくとも2.5ではSyntaxWarningが生成されます。
リックコープランド

3

sqlalchemyで使用される代替アプローチを見てください:依存関係の注入:

@util.dependencies("sqlalchemy.orm.query")
def merge_result(query, *args):
    #...
    query.Query(...)

インポートされたライブラリがデコレータで宣言され、引数として関数に渡されていることに注意してください。

このアプローチにより、コードがよりクリーンになり、ステートメントより4.5倍速く動作しimportます。

ベンチマーク:https : //gist.github.com/kolypto/589e84fbcfb6312532658df2fabdb796


2

両方とも「通常の」モジュールであり、実行可能な(つまり、if __name__ == '__main__':-sectionがある)モジュールでは、通常、メインセクション内でモジュールを実行するときにのみ使用されるモジュールをインポートします。

例:

def really_useful_function(data):
    ...


def main():
    from pathlib import Path
    from argparse import ArgumentParser
    from dataloader import load_data_from_directory

    parser = ArgumentParser()
    parser.add_argument('directory')
    args = parser.parse_args()
    data = load_data_from_directory(Path(args.directory))
    print(really_useful_function(data)


if __name__ == '__main__':
    main()

1

importまれにしか使用されない関数の内部にメリットがある別の(おそらく「コーナー」)ケースがあります。起動時間の短縮です。

小さなIoTサーバーで実行されているかなり複雑なプログラムがシリアルラインからのコマンドを受け入れて操作を実行し、おそらく非常に複雑な操作を実行して、その壁に一度ぶつかりました。

サーバーの起動前にすべてのインポートを処理importすることを意図したファイルの先頭にステートメントを配置します。以来、リストが含まれ、、およびその他の「ヘビーウエイト」この意味(およびSoCの非常に強力ではなかった)分、最初の命令が実際に実行される前に。importjinja2lxmlsignxml

ほとんどのインポートを関数に配置するOTOHは、シリアルライン上でサーバーを数秒で「稼働」させることができました。もちろん、モジュールが実際に必要なときは、代金を支払う必要がありました(注:これはimport、アイドル時間にsを実行しているバックグラウンドタスクを生成することによって軽減することもできます)。

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