Mengimpor dari pustaka bawaan saat modul dengan nama yang sama ada

121

Situasi: - Ada modul di project_folder saya yang disebut kalender - Saya ingin menggunakan kelas Kalender bawaan dari perpustakaan Python - Ketika saya menggunakan kalender impor kalender mengeluh karena mencoba memuat dari modul saya.

Saya telah melakukan beberapa pencarian dan sepertinya saya tidak dapat menemukan solusi untuk masalah saya.

Ada ide tanpa harus mengganti nama modul saya?

ranting
sumber
24
Ini adalah praktik terbaik untuk tidak memberi nama modul untuk menyembunyikan modul bawaan.
the_drow
3
Solusinya adalah "pilih nama lain". Pendekatan Anda untuk tidak mengganti nama adalah ide yang buruk. Mengapa Anda tidak dapat mengganti nama modul Anda? Apa yang salah dengan mengganti nama?
S. Lotot
Memang. Itu karena tidak ada jawaban yang baik untuk pertanyaan ini sehingga membayangi modul stdlib sangat tidak disarankan.
ncoghlan
Saya menghindari menggunakan nama modul yang sama karena solusinya tampak lebih merepotkan daripada nilainya. Terima kasih!
ranting
9
@ the_drow Nasihat ini tidak berskala, murni dan sederhana. PEP328 dengan mudah mengakui ini.
Konrad Rudolph

Jawaban:

4

Solusi yang diterima berisi pendekatan yang sekarang tidak digunakan lagi.

Dokumentasi importlib di sini memberikan contoh yang baik tentang cara yang lebih tepat untuk memuat modul langsung dari jalur file untuk 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)

Jadi, Anda dapat memuat file .py apa pun dari jalur dan mengatur nama modul menjadi apa pun yang Anda inginkan. Jadi sesuaikan saja module_namemenjadi nama kustom apa pun yang Anda inginkan untuk modul saat mengimpor.

Untuk memuat paket, bukan satu file, file_pathharus menjadi jalur ke root paket__init__.py

Brandon Squizzato
sumber
Bekerja seperti pesona ... Digunakan ini untuk menguji sambil mengembangkan perpustakaan, sehingga pengujian saya selalu menggunakan versi pengembangan dan bukan versi yang dipublikasikan (dan diinstal). Di windows 10 saya harus menulis jalan untuk modul saya seperti ini: file_path=r"C:\Users\My User\My Path\Module File.py". Kemudian saya menelepon module_nameseperti modul yang dirilis sehingga saya memiliki skrip kerja penuh yang, tanpa potongan ini, dapat digunakan pada PC lain
Luke Savefrogs
141

Tidak perlu mengubah nama modul Anda. Sebaliknya, Anda dapat menggunakan absolute_import untuk mengubah perilaku impor. Misalnya dengan stem / socket.py saya mengimpor modul socket sebagai berikut:

from __future__ import absolute_import
import socket

Ini hanya bekerja dengan Python 2.5 ke atas; itu mengaktifkan perilaku yang merupakan default di Python 3.0 dan lebih tinggi. Pylint akan mengeluh tentang kode tersebut tetapi itu benar-benar valid.

Damian
sumber
4
Ini sepertinya jawaban yang benar bagi saya. Lihat log perubahan 2.5 atau PEP328 untuk lebih lanjut.
Pieter Ennes
5
Ini adalah solusi yang tepat. Sayangnya, ini tidak berfungsi ketika kode dari dalam paket diluncurkan karena paket tidak dikenali seperti itu, dan jalur lokal ditambahkan di awal PYTHONPATH. Pertanyaan lain menunjukkan bagaimana menyelesaikannya.
Konrad Rudolph
5
Inilah solusinya. Saya memeriksa Python 2.7.6 dan ini diperlukan, ini masih bukan default.
Havok
3
Memang: Versi python pertama di mana perilaku ini adalah defaultnya adalah 3.0, menurut docs.python.org/2/library/__future__.html
salah kaprah
1
Kemudian jangan menamai modul utama Anda sebagai modul yang bentrok dengan modul bawaan.
Antti Haapala
38

Sebenarnya, menyelesaikan ini agak mudah, tetapi implementasinya akan selalu sedikit rapuh, karena tergantung internal mekanisme impor python dan dapat berubah di versi mendatang.

(kode berikut menunjukkan cara memuat modul lokal dan non-lokal dan bagaimana keduanya dapat hidup berdampingan)

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

Solusi terbaik, jika memungkinkan, adalah menghindari menamai modul Anda dengan nama yang sama dengan pustaka standar atau nama modul bawaan.

Boaz Yaniv
sumber
Bagaimana ini akan berinteraksi dengan sys.modulesdan upaya selanjutnya untuk memuat modul lokal?
Omnifarious
@Omnifarious: Ini akan menambahkan modul ke sys.modules dengan namanya, yang akan mencegah memuat modul lokal. Anda selalu dapat menggunakan nama khusus untuk menghindarinya.
Boaz Yaniv
@Boaz Yaniv: Anda harus menggunakan nama khusus untuk kalender lokal, bukan yang standar. Modul Python lain mungkin mencoba mengimpor yang standar. Dan jika Anda melakukan itu, yang Anda capai dengan ini pada dasarnya adalah mengganti nama modul lokal tanpa harus mengganti nama file.
Omnifarious
@Omnifarious: Anda bisa melakukannya dengan cara apa pun. Beberapa kode lain mungkin mencoba memuat modul lokal dan mendapatkan kesalahan yang sama. Anda harus membuat kompromi, dan terserah Anda untuk memutuskan modul mana yang akan didukung.
Boaz Yaniv
2
Terima kasih untuk Boas itu! Meskipun potongan Anda lebih pendek (dan dokumen), menurut saya itu lebih mudah untuk mengganti nama modul daripada memiliki beberapa kode hacky yang dapat membingungkan orang (atau saya sendiri) di masa depan.
ranting
15

Satu-satunya cara untuk mengatasi masalah ini adalah dengan membajak sendiri mesin impor internal. Ini tidak mudah, dan penuh bahaya. Anda harus menghindari suar berbentuk grail dengan segala cara karena bahayanya terlalu berbahaya.

Ganti nama modul Anda sebagai gantinya.

Jika Anda ingin mempelajari cara membajak mesin impor internal, inilah tempat yang akan Anda tuju untuk mengetahui cara melakukan ini:

Terkadang ada alasan bagus untuk terlibat dalam bahaya ini. Alasan Anda memberi tidak ada di antara mereka. Ubah nama modul Anda.

Jika Anda mengambil jalur yang berbahaya, satu masalah yang akan Anda temui adalah ketika Anda memuat modul, modul itu berakhir dengan 'nama resmi' sehingga Python dapat menghindari keharusan mengurai konten modul itu lagi. Pemetaan 'nama resmi' modul ke objek modul itu sendiri dapat ditemukan di sys.modules.

Ini berarti bahwa jika Anda berada import calendardi satu tempat, modul apa pun yang diimpor akan dianggap sebagai modul dengan nama resmi calendardan semua upaya lain ke import calendartempat lain, termasuk di kode lain yang merupakan bagian dari pustaka utama Python, akan mendapatkan kalender itu.

Dimungkinkan untuk mendesain importir pelanggan menggunakan modul imputil dengan Python 2.x yang menyebabkan modul dimuat dari jalur tertentu untuk mencari modul yang mereka impor dalam sesuatu selain yang sys.modulespertama atau sesuatu seperti itu. Tapi itu hal yang sangat sulit untuk dilakukan, dan itu tidak akan berhasil dengan Python 3.x.

Ada hal yang sangat jelek dan mengerikan yang dapat Anda lakukan tanpa melibatkan mekanisme impor. Ini adalah sesuatu yang mungkin tidak Anda lakukan, tetapi kemungkinan besar akan berhasil. Ini mengubah calendarmodul Anda menjadi hibrida dari modul kalender sistem dan modul kalender Anda. Terima kasih kepada Boaz Yaniv untuk kerangka fungsi yang saya gunakan . Letakkan ini di awal calendar.pyfile Anda :

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__])
Beraneka ragam
sumber
imputil dianggap usang. Anda harus menggunakan modul imp .
Boaz Yaniv
Yang sangat kompatibel dengan Python 3. Dan tidak terlalu berbulu untuk digunakan sama sekali. Tetapi Anda harus selalu menyadari bahwa kode yang bergantung pada python memperlakukan jalur dengan satu cara atau mencari modul dalam urutan itu dapat rusak cepat atau lambat.
Boaz Yaniv
1
Benar, tetapi dalam kasus yang terisolasi (tabrakan nama modul) mengaitkan mekanisme impor adalah berlebihan. Dan karena berbulu dan tidak cocok, lebih baik dibiarkan saja.
Boaz Yaniv
1
@jspacek tidak, sejauh ini bagus, tetapi tabrakan hanya akan terjadi saat menggunakan debbuger PyDev, tidak dalam penggunaan biasa. Dan pastikan Anda memeriksa kode terbaru (URL di github), karena itu sedikit berubah dari jawaban di atas
MestreLion
1
@jspacek: Ini adalah permainan, bukan perpustakaan, jadi dalam kasus saya kompatibilitas ke belakang sama sekali tidak menjadi perhatian. Dan tabrakan namespace hanya terjadi saat menggunakan berjalan melalui PyDev IDE (yang menggunakan codemodul std Python ), yang berarti hanya sebagian kecil pengembang yang pernah memiliki masalah dengan "merging hack" ini. Pengguna tidak akan terpengaruh sama sekali.
MestreLion
1

Saya ingin menawarkan versi saya, yang merupakan kombinasi dari solusi Boaz Yaniv dan Omnifarious. Ini akan mengimpor versi sistem dari modul, dengan dua perbedaan utama dari jawaban sebelumnya:

  • Mendukung notasi 'titik', mis. package.module
  • Merupakan pengganti drop-in untuk pernyataan import pada modul sistem, yang berarti Anda hanya perlu mengganti satu baris itu dan jika sudah ada panggilan yang dilakukan ke modul, mereka akan bekerja sebagaimana adanya

Letakkan ini di suatu tempat yang dapat diakses sehingga Anda dapat memanggilnya (saya punya milik saya di file __init__.py saya):

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

Contoh

Saya ingin mengimpor mysql.connection, tetapi saya memiliki paket lokal yang sudah disebut mysql (utilitas resmi mysql). Jadi untuk mendapatkan konektor dari paket sistem mysql, saya mengganti ini:

import mysql.connector

Dengan ini:

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__])

Hasil

# 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)
casey
sumber
-2

Ubah jalur impor:

import sys
save_path = sys.path[:]
sys.path.remove('')
import calendar
sys.path = save_path
linuts
sumber
Ini tidak akan berhasil karena setelah melakukan ini tidak akan ada cara untuk mengimpor modul lokal tanpa pergi dan mengutak-atik mesin impor sendiri.
Omnifarious
@Omnifarious: itu adalah masalah yang berbeda, yang dapat Anda atasi dengan modul ketiga yang melakukan impor kalender *.
linuts
Tidak, ini mungkin tidak akan berhasil karena python menyimpan nama modul dalam cache sys.modules, dan tidak akan mengimpor modul dengan nama yang sama lagi.
Boaz Yaniv