同じ名前のモジュールが存在する場合の組み込みライブラリからのインポート


121

状況:-私のproject_folderにcalendarというモジュールがあります-Pythonライブラリの組み込みのCalendarクラスを使用したいです-カレンダーインポートカレンダーから使用すると、モジュールからロードしようとしているため、文句を言われます。

何回か検索を行いましたが、問題の解決策を見つけることができません。

モジュールの名前を変更する必要がないアイデアはありますか?


24
組み込みモジュールを隠すためにモジュールに名前を付けないことがベストプラクティスです。
the_drow

3
解決策は、「別の名前を選択する」ことです。名前を変更しないというあなたのアプローチは悪い考えです。モジュールの名前を変更できないのはなぜですか?名前の変更の何が問題になっていますか?
S.Lott

確かに。これは、stdlibモジュールのシャドウイングが非常に推奨されないのは、この質問に対する適切な回答がないためです。
ncoghlan '17年

同じモジュール名を使用することは避けました。解決策はそれよりも問題が多いようです。ありがとう!
小枝

9
@the_drowこのアドバイスはスケーリングされず、純粋で単純です。PEP328はこれをすぐに認めます。
Konrad Rudolph

回答:


4

承認されたソリューションには、現在非推奨のアプローチが含まれています。

importlibドキュメントは、ここでのpython> = 3.5のファイルパスから直接モジュールをロードするために、より適切な方法の良い例を示します:

import importlib.util
import sys

# For illustrative purposes.
import tokenize
file_path = tokenize.__file__  # returns "/path/to/tokenize.py"
module_name = tokenize.__name__  # returns "tokenize"

spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)

したがって、パスから任意の.pyファイルをロードし、モジュール名を任意の名前に設定できます。したがってmodule_name、インポート時にモジュールに付ける任意のカスタム名になるように調整します。

単一のファイルの代わりにパッケージをロードするfile_pathには、パッケージのルートへのパスである必要があります__init__.py


チャームのように動作します...これを使用して、ライブラリの開発中にテストを行いました。そのため、私のテストでは常に公開(およびインストール)バージョンではなく、開発バージョンを使用していました。Windows 10では、次のようにモジュールへのパスを記述する必要がありましたfile_path=r"C:\Users\My User\My Path\Module File.py"。次にmodule_name、リリースされたモジュールと同じように呼び出し、このスニペットを取り除いて、他のPCで使用できる完全なスクリプトを作成しました
Luke Savefrogs

141

モジュールの名前を変更する必要はありません。むしろ、absolute_importを使用してインポート動作を変更できます。たとえば、stem / socket.pyでは、次のようにソケットモジュールをインポートします。

from __future__ import absolute_import
import socket

これはPython 2.5以降でのみ機能します。Python 3.0以降のデフォルトの動作を有効にします。Pylintはコードについて文句を言うでしょうが、それは完全に有効です。


4
これは私にとって正しい答えのようです。詳細については、2.5の変更ログまたはPEP328を参照してください。
Pieter Ennes、2012年

5
これが正しい解決策です。残念ながら、パッケージ内からコードを起動すると、パッケージがそのように認識されず、ローカルパスがに付加されるため、機能しませんPYTHONPATH別の質問はそれを解決する方法を示しています。
コンラートルドルフ

5
これが解決策です。私はPython 2.7.6をチェックしましたが、これは必須ですが、それはまだデフォルトではありません。
Havok

3
実際:この動作はデフォルトで最初のPythonのバージョンはによると、3.0であったdocs.python.org/2/library/__future__.html
誤称

1
次に、メインモジュールに組み込みモジュールと競合する名前を付けないでください。
Antti Haapala

38

実際、これを解決するのはかなり簡単ですが、Pythonインポートメカニズムの内部に依存し、将来のバージョンで変更される可能性があるため、実装は常に少し脆弱です。

(次のコードは、ローカルモジュールと非ローカルモジュールの両方をロードする方法と、それらがどのように共存するかを示しています)

def import_non_local(name, custom_name=None):
    import imp, sys

    custom_name = custom_name or name

    f, pathname, desc = imp.find_module(name, sys.path[1:])
    module = imp.load_module(custom_name, f, pathname, desc)
    f.close()

    return module

# Import non-local module, use a custom name to differentiate it from local
# This name is only used internally for identifying the module. We decide
# the name in the local scope by assigning it to the variable calendar.
calendar = import_non_local('calendar','std_calendar')

# import local module normally, as calendar_local
import calendar as calendar_local

print calendar.Calendar
print calendar_local

可能であれば、標準ライブラリまたは組み込みモジュールの名前と同じ名前でモジュールに名前を付けないようにすることをお勧めします。


これはどのように相互作用しsys.modules、その後ローカルモジュールをロードする試みを行いますか?
全面的

@Omnifarious:モジュールをsys.modulesにその名前で追加します。これにより、ローカルモジュールのロードが防止されます。これを避けるために、いつでもカスタム名を使用できます。
ボアズヤニフ、

@Boaz Yaniv:ローカルカレンダーには、標準の名前ではなく、カスタム名を使用する必要があります。他のPythonモジュールは、標準のモジュールをインポートしようとする場合があります。その場合、基本的にはファイルの名前を変更せずにローカルモジュールの名前を変更します。
全面的に

@Omnifarious:どちらの方法でもできます。他のコードがローカルモジュールをロードしようとして、まったく同じエラーが発生する場合があります。妥協する必要があり、どのモジュールをサポートするかはあなた次第です。
ボアズヤニフ、

2
そのボアズをありがとう!スニペットは短く(そしてドキュメント化されています)、モジュールの名前を変更する方が、将来人々(または私自身)を混乱させる可能性のあるハックコードよりも簡単だと思います。
小枝'17年

15

この問題を解決する唯一の方法は、内部の輸入機械を自分でハイジャックすることです。これは簡単ではなく、危険に満ちています。危険は非常に危険なので、杯形のビーコンは絶対に避けてください。

代わりにモジュールの名前を変更してください。

内部のインポート機構をハイジャックする方法を学びたい場合は、これを行う方法を見つけるためにどこに行くでしょう:

この危険に陥る理由は時々あります。あなたが与える理由はそれらの間ではありません。モジュールの名前を変更します。

危険なパスをたどると、1つの問題が発生します。これは、モジュールをロードするときに、「公式名」で終わるため、Pythonがそのモジュールの内容を再度解析する必要がなくなるということです。モジュールの「公式名」とモジュールオブジェクト自体のマッピングは、にありますsys.modules

つまりimport calendar、1か所にある場合、インポートされたモジュールは公式名のモジュールと見なされ、メインのPythonライブラリの一部である他のコードを含め、他の場所への他のcalendarすべての試みimport calendarはそのカレンダーを取得します。

Python 2.xのimputilモジュールを使用してカスタマーインポーターを設計すると、特定のパスからロードされたモジュールが、インポートしているモジュールをsys.modules最初とは別のものなどで検索する可能性があります。しかし、これは非常に厄介なことであり、Python 3.xでは動作しません。

インポートメカニズムのフックを含まない、あなたができる非常に醜くて恐ろしいことがあります。これはおそらくすべきではないことですが、おそらく機能します。それはあなたのcalendarモジュールをシステムカレンダーモジュールとあなたのカレンダーモジュールのハイブリッドに変えます。私が使用する関数スケルトンを提供してくれたBoaz Yanivに感謝します。これをcalendar.pyファイルの先頭に置きます:

import sys

def copy_in_standard_module_symbols(name, local_module):
    import imp

    for i in range(0, 100):
        random_name = 'random_name_%d' % (i,)
        if random_name not in sys.modules:
            break
        else:
            random_name = None
    if random_name is None:
        raise RuntimeError("Couldn't manufacture an unused module name.")
    f, pathname, desc = imp.find_module(name, sys.path[1:])
    module = imp.load_module(random_name, f, pathname, desc)
    f.close()
    del sys.modules[random_name]
    for key in module.__dict__:
        if not hasattr(local_module, key):
            setattr(local_module, key, getattr(module, key))

copy_in_standard_module_symbols('calendar', sys.modules[copy_in_standard_module_symbols.__module__])

imputilは推奨されていません。impモジュールを使用する必要があります。
ボアズヤニフ、

ところで、これはPython 3と完全に互換性があります。毛むくじゃないです。ただし、Pythonがパスを処理する方法に依存するコードや、この順序でモジュールを検索するコードは、遅かれ早かれ壊れることがあることに常に注意する必要があります。
ボアズヤニフ

1
そうですが、そのような孤立したケース(モジュール名の衝突)では、インポートメカニズムのフックはやりすぎです。また、毛が多く互換性がないため、そのままにしておくことをお勧めします。
ボアズヤニフ、

1
@jspacekいいえ、これまでのところ良好ですが、衝突はPyDevのデバッガを使用している場合にのみ発生し、通常の使用では発生しません。そして、それは上記の回答から少し変更するので、あなたは、最新のコード(githubのでURL)を確認してください
MestreLion

1
@jspacek:ライブラリではなくゲームなので、私の場合、下位互換性はまったく問題になりません。また、名前空間の衝突は、PyDev IDE(Pythonのcodestdモジュールを使用)を介して実行している場合にのみ発生します。つまり、この「マージハッキング」で問題が発生する可能性があるのはごく一部の開発者だけです。ユーザーはまったく影響を受けません。
MestreLion 2014年

1

Boaz YanivとOmnifariousのソリューションを組み合わせたバージョンを提供したいと思います。以前の回答との主な違いが2つありますが、モジュールのシステムバージョンがインポートされます。

  • 「ドット」表記をサポートします。package.module
  • システムモジュールのインポートステートメントの置き換えです。つまり、その1行を置き換えるだけで、モジュールに対して既に呼び出しが行われている場合は、そのまま機能します。

あなたがそれを呼び出すことができるようにアクセス可能などこかにこれを置いてください(私は私の__init__.pyファイルに私のものを持っています):

class SysModule(object):
    pass

def import_non_local(name, local_module=None, path=None, full_name=None, accessor=SysModule()):
    import imp, sys, os

    path = path or sys.path[1:]
    if isinstance(path, basestring):
        path = [path]

    if '.' in name:
        package_name = name.split('.')[0]
        f, pathname, desc = imp.find_module(package_name, path)
        if pathname not in __path__:
            __path__.insert(0, pathname)
        imp.load_module(package_name, f, pathname, desc)
        v = import_non_local('.'.join(name.split('.')[1:]), None, pathname, name, SysModule())
        setattr(accessor, package_name, v)
        if local_module:
            for key in accessor.__dict__.keys():
                setattr(local_module, key, getattr(accessor, key))
        return accessor
    try:
        f, pathname, desc = imp.find_module(name, path)
        if pathname not in __path__:
            __path__.insert(0, pathname)
        module = imp.load_module(name, f, pathname, desc)
        setattr(accessor, name, module)
        if local_module:
            for key in accessor.__dict__.keys():
                setattr(local_module, key, getattr(accessor, key))
            return module
        return accessor
    finally:
        try:
            if f:
                f.close()
        except:
            pass

mysql.connectionをインポートしたいのですが、すでにmysql(公式のmysqlユーティリティ)と呼ばれるローカルパッケージがありました。したがって、システムのmysqlパッケージからコネクタを取得するために、これを置き換えました。

import mysql.connector

これとともに:

import sys
from mysql.utilities import import_non_local         # where I put the above function (mysql/utilities/__init__.py)
import_non_local('mysql.connector', sys.modules[__name__])

結果

# This unmodified line further down in the file now works just fine because mysql.connector has actually become part of the namespace
self.db_conn = mysql.connector.connect(**parameters)

-2

インポートパスを変更します。

import sys
save_path = sys.path[:]
sys.path.remove('')
import calendar
sys.path = save_path

これを行った後は、自分でインポート機構をいじってみないとローカルモジュールをインポートする方法がないため、これは機能しません。
全面的に

@Omnifarious:これは別の問題です。カレンダーからのインポートを実行する3番目のモジュールで回避できます*。
リナツ

いいえ、Pythonではモジュール名がにキャッシュされるため、これはおそらく機能しませんsys.modules。また、同じ名前のモジュールを再度インポートすることはありません。
ボアズヤニフ、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.