Aynı isimde modül varken yerleşik kütüphaneden içe aktarma


121

Durum: - Proje klasörümde takvim adında bir modül var - Python kitaplıklarından yerleşik Takvim sınıfını kullanmak istiyorum - Takvim içe aktarma Takviminden kullandığımda, modülümden yüklemeye çalıştığı için şikayet ediyor.

Birkaç arama yaptım ve sorunuma bir çözüm bulamıyorum.

Modülümü yeniden adlandırmak zorunda kalmadan herhangi bir fikriniz var mı?


24
Yerleşik modülleri gizlemek için modülleri adlandırmamak en iyi uygulamadır.
the_drow

3
Çözüm "farklı bir isim seç" dir. Yeniden adlandırmama yaklaşımınız kötü bir fikir. Modülünüzü neden yeniden adlandıramıyorsunuz? Yeniden adlandırmanın nesi yanlış?
S.Lott

Aslında. Orada çünkü öyle hiçbir stdlib modüllerini gölgeleme yüzden kesinlikle önerilmez bu soruya iyi bir cevap.
ncoghlan

Çözümler değerinden daha fazla sorun gibi göründüğü için aynı modül adını kullanmaktan kaçındım. Teşekkürler!
dal

9
@the_drow Bu tavsiye ölçülü değil, saf ve basit. PEP328 bunu kolaylıkla kabul eder.
Konrad Rudolph

Yanıtlar:


4

Kabul edilen çözüm, artık kullanımdan kaldırılmış bir yaklaşım içermektedir.

İmportlib belgeleri burada piton> = 3.5 için bir dosya yolu doğrudan bir modül yüklemek için daha uygun bir şekilde iyi bir örnek verir:

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)

Böylece, bir yoldan herhangi bir .py dosyasını yükleyebilir ve modül adını istediğiniz gibi ayarlayabilirsiniz. Bu yüzden module_name, modülün içe aktarırken sahip olmasını istediğiniz özel adı ayarlayın .

Tek bir dosya yerine bir paket yüklemek file_path, paketin köküne giden yol olmalıdır__init__.py


Tılsım gibi çalışıyor ... Bunu bir kütüphane geliştirirken test etmek için kullandım, böylece testlerim her zaman yayınlanan (ve yüklenen) değil, gelişen sürümü kullanıyordu. Pencerelerde 10 yılında ben böyle Modülümde yolunu yazmak zorunda: file_path=r"C:\Users\My User\My Path\Module File.py". Sonra module_nametıpkı yayımlanan modül gibi aradım , böylece tam çalışan bir betiğe
sahiptim

141

Modülünüzün adını değiştirmek gerekli değildir. Bunun yerine, içe aktarma davranışını değiştirmek için absolute_import kullanabilirsiniz. Örneğin, stem / socket.py ile soket modülünü şu şekilde içe aktarıyorum :

from __future__ import absolute_import
import socket

Bu yalnızca Python 2.5 ve üzeri ile çalışır; Python 3.0 ve sonraki sürümlerde varsayılan olan etkinleştirme davranışıdır. Pylint koddan şikayet edecek ama tamamen geçerli.


4
Bu bana doğru cevap gibi görünüyor. Daha fazlası için 2.5 değişiklik günlüğüne veya PEP328'e bakın.
Pieter Ennes

5
Doğru çözüm budur. Ne yazık ki, paketin içinden kod başlatıldığında çalışmaz, çünkü bu durumda paket bu şekilde tanınmaz ve yerel yolun başına eklenir PYTHONPATH. Başka bir soru bunun nasıl çözüleceğini gösteriyor.
Konrad Rudolph

5
Çözüm bu. Python 2.7.6'yı kontrol ettim ve bu gerekli, yine de varsayılan değil.
Havok


1
O zaman ana modülünüzü yerleşik bir modülle çakışan bir modül olarak adlandırmayın.
Antti Haapala

38

Aslında bunu çözmek oldukça kolaydır, ancak uygulama her zaman biraz kırılgan olacaktır, çünkü bu python içe aktarma mekanizmasının iç bileşenlerine bağlıdır ve gelecekteki sürümlerde değişebilir.

(aşağıdaki kod, hem yerel hem de yerel olmayan modüllerin nasıl yükleneceğini ve nasıl bir arada bulunabileceklerini gösterir)

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

Mümkünse en iyi çözüm, modüllerinizi standart kitaplık veya yerleşik modül adlarıyla aynı adla adlandırmaktan kaçınmaktır.


Bu sys.modules, yerel modülü yükleme girişimleriyle nasıl etkileşim kuracak ?
Omnifarious

@Omnifarious: Modülü adıyla birlikte sys.modules'e ekleyerek yerel modülün yüklenmesini engelleyecektir. Bundan kaçınmak için her zaman özel bir ad kullanabilirsiniz.
Boaz Yaniv

@Boaz Yaniv: Yerel takvim için standart değil, özel bir ad kullanmalısınız. Diğer Python modülleri standart olanı içe aktarmayı deneyebilir. Ve bunu yaparsanız, bununla elde ettiğiniz şey, dosyayı yeniden adlandırmak zorunda kalmadan yerel modülü temelde yeniden adlandırmaktır.
Omnifarious

@Omnifarious: Her iki şekilde de yapabilirsin. Başka bir kod yerel modülü yüklemeyi deneyebilir ve aynı hatayı alabilir. Bir uzlaşma sağlamanız gerekecek ve hangi modülü destekleyeceğinize karar vermek size kalmış.
Boaz Yaniv

2
Bunun için teşekkürler Boaz! Snippet'iniz daha kısa (ve belge) olmasına rağmen, modülü yeniden adlandırmanın, gelecekte insanların (veya kendimin) kafasını karıştırabilecek bazı hacky kodlara sahip olmaktan daha kolay olduğunu düşünüyorum.
dal

15

Bu sorunu çözmenin tek yolu, dahili ithalat makinelerini kendi başınıza ele geçirmektir. Bu kolay değil ve tehlikelerle dolu. Tehlike çok tehlikeli olduğu için her ne pahasına olursa olsun kase şeklindeki işaretten kaçınmalısınız.

Bunun yerine modülünüzü yeniden adlandırın.

İç ithalat makinelerini nasıl ele geçireceğinizi öğrenmek istiyorsanız, işte bunu nasıl yapacağınızı öğrenmek için gideceğiniz yer:

Bazen bu tehlikeye girmek için iyi nedenler vardır. Verme sebebi onların arasında değil. Modülünüzü yeniden adlandırın.

Tehlikeli yolu seçerseniz, karşılaşacağınız sorunlardan biri, bir modülü yüklediğinizde 'resmi bir ad' ile bitmesidir, böylece Python, o modülün içeriğini bir daha ayrıştırmak zorunda kalmaz. Bir modülün 'resmi adı' ile modül nesnesinin kendisinin bir eşlemesi içinde bulunabilir sys.modules.

Bu import calendar, bir yerde olursanız , içe aktarılan modülün resmi adı olan modül olarak düşünülecek ve ana Python kütüphanesinin parçası olan diğer kodlar da dahil olmak üzere başka herhangi bir yere yapılan calendardiğer tüm girişimler import calendarbu takvimi alacağı anlamına gelir.

Python 2.x'teki imputil modülünü kullanarak bir müşteri ithalatçısının tasarlanması , belirli yollardan yüklenen modüllerin içeri aktardıkları modülleri sys.modulesilk veya bunun gibi bir şey dışında aramasına neden olabilir. Ancak bu, yapılması gereken son derece tüylü bir şey ve Python 3.x'te zaten çalışmayacak.

İçe aktarma mekanizmasını bağlamadan yapabileceğiniz son derece çirkin ve korkunç bir şey var. Bu muhtemelen yapmamanız gereken bir şey, ancak muhtemelen işe yarayacaktır. Bu sizin döner calendarbir sistem takvim modülünün melez ve takvim modülü içine modülü. Sayesinde Boaz Yaniv için işlev ben kullanımının iskeleti . Bunu calendar.pydosyanızın başına koyun :

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 kullanımdan kaldırılmış kabul edilir. İmp modülünü kullanmalısınız .
Boaz Yaniv

Bu arada, Python 3 ile mükemmel uyumludur. Ve o kadar da kıllı değil. Ancak, python'un yolları tek bir şekilde işlemesine veya bu sırayla modüller aranmasına dayanan kodun er ya da geç bozulabileceğini her zaman bilmelisiniz.
Boaz Yaniv

1
Doğru, ancak böyle izole bir durumda (modül adı çakışması) içe aktarma mekanizmasını bağlamak aşırı bir şeydir. Tüylü ve uyumsuz olduğu için, yalnız bırakılması daha iyi.
Boaz Yaniv

1
@jspacek hayır, şimdiye kadar çok iyi, ancak çakışma yalnızca PyDev'in hata ayıklayıcısını kullanırken meydana gelebilir, normal kullanımda değil. Yukarıdaki yanıttan biraz değiştiği için en son kodu (
github'daki

1
@jspacek: Bu bir oyun, kütüphane değil, yani benim durumumda geriye dönük uyumluluk hiç de endişe verici değil. Ve ad alanı çarpışması yalnızca PyDev IDE (Python'un codestd modülünü kullanır) aracılığıyla çalıştırılırken meydana gelir , yani geliştiricilerin yalnızca bir kısmının bu "birleştirme saldırısı" ile herhangi bir sorunu olabileceği anlamına gelir . Kullanıcılar hiç etkilenmeyecektir.
MestreLion

1

Boaz Yaniv ve Omnifarious'un çözümünün bir kombinasyonu olan versiyonumu sunmak istiyorum. Önceki yanıtlardan iki temel farkla bir modülün sistem sürümünü içe aktaracaktır:

  • "Nokta" gösterimini destekler, örn. package.module
  • Sistem modüllerindeki içe aktarma ifadesinin yerine geçmesidir, yani sadece bir satırı değiştirmeniz gerekir ve modüle zaten yapılan çağrılar varsa, olduğu gibi çalışacaktır.

Bunu arayabilmeniz için erişilebilir bir yere koyun (benim __init__.py dosyamda var):

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

Misal

Mysql.connection dosyasını içe aktarmak istedim, ancak zaten mysql (resmi mysql yardımcı programları) adında yerel bir paketim vardı. Bu yüzden konektörü sistem mysql paketinden almak için bunu değiştirdim:

import mysql.connector

Bununla:

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

Sonuç

# 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

İçe aktarma yolunu değiştirin:

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

Bu işe yaramayacak, çünkü bunu yaptıktan sonra, ithalat makinesiyle uğraşmadan yerel modülü ithal etmenin bir yolu olmayacak.
Omnifarious

@Omnifarious: Bu, takvimden içe aktarım yapan üçüncü bir modülle karşılaşabileceğiniz farklı bir sorundur *.
linuts

Hayır, bu muhtemelen işe yaramaz çünkü python modül adını önbelleğe alır sys.modulesve aynı ada sahip bir modülü tekrar içe aktarmaz.
Boaz Yaniv
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.