การนำเข้าจากไลบรารีในตัวเมื่อมีโมดูลที่มีชื่อเดียวกัน


121

สถานการณ์: - มีโมดูลใน project_folder ของฉันชื่อปฏิทิน - ฉันต้องการใช้คลาสปฏิทินในตัวจากไลบรารี Python - เมื่อฉันใช้จากการนำเข้าปฏิทินปฏิทินมันบ่นเพราะพยายามโหลดจากโมดูลของฉัน

ฉันได้ทำการค้นหาสองสามครั้งแล้วและดูเหมือนจะหาวิธีแก้ปัญหาไม่ได้

มีไอเดียใดบ้างที่ไม่ต้องเปลี่ยนชื่อโมดูลของฉัน?


24
เป็นแนวทางปฏิบัติที่ดีที่สุดที่จะไม่ตั้งชื่อโมดูลเพื่อซ่อนโมดูลในตัว
the_drow

3
วิธีแก้ปัญหาคือ "เลือกชื่ออื่น" วิธีการไม่เปลี่ยนชื่อของคุณเป็นความคิดที่ไม่ดี ทำไมคุณไม่สามารถเปลี่ยนชื่อโมดูลของคุณได้? เกิดอะไรขึ้นกับการเปลี่ยนชื่อ?
S.Lott

จริง เป็นเพราะไม่มีคำตอบที่ดีสำหรับคำถามนี้ที่ทำให้โมดูล stdlib เป็นเงาเป็นสิ่งที่ไม่ควรทำอย่างยิ่ง
ncoghlan

ฉันหลีกเลี่ยงการใช้ชื่อโมดูลเดียวกันเนื่องจากโซลูชันดูเหมือนจะมีปัญหามากกว่าที่ควรจะเป็น ขอบคุณ!
กิ่งไม้

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เช่นเดียวกับโมดูลที่ปล่อยออกมาเพื่อให้ฉันมีสคริปต์ทำงานเต็มรูปแบบที่ถอดตัวอย่างนี้ออกมาใช้กับชิ้นส่วนอื่น ๆ
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

5
นี่คือวิธีแก้ปัญหาที่ถูกต้อง น่าเสียดายที่ไม่สามารถใช้งานได้เมื่อเปิดใช้งานโค้ดจากภายในแพ็กเกจเนื่องจากไม่รู้จักแพ็กเกจดังกล่าวและเส้นทางภายในเครื่องจะอยู่PYTHONPATHข้างหน้า คำถามอื่นแสดงวิธีแก้ปัญหานั้น
Konrad Rudolph

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

@Omnifarious: จะเพิ่มโมดูลลงใน sys.modules พร้อมชื่อซึ่งจะป้องกันไม่ให้โหลดโมดูลภายในเครื่อง คุณสามารถใช้ชื่อที่กำหนดเองเพื่อหลีกเลี่ยงสิ่งนั้นได้เสมอ
Boaz Yaniv

@Boaz Yaniv: คุณควรใช้ชื่อที่กำหนดเองสำหรับปฏิทินท้องถิ่นไม่ใช่ชื่อมาตรฐาน โมดูล Python อื่น ๆ อาจพยายามนำเข้าโมดูลมาตรฐาน และถ้าคุณทำเช่นนั้นสิ่งที่คุณทำได้คือการเปลี่ยนชื่อโมดูลภายในโดยทั่วไปโดยไม่ต้องเปลี่ยนชื่อไฟล์
Omnifarious

@Omnifarious: คุณสามารถทำได้ไม่ว่าจะด้วยวิธีใด รหัสอื่นบางรหัสอาจพยายามโหลดโมดูลภายในเครื่องและได้รับข้อผิดพลาดเดียวกัน คุณจะต้องประนีประนอมและขึ้นอยู่กับคุณว่าจะสนับสนุนโมดูลใด
Boaz Yaniv

2
ขอบคุณสำหรับโบอาส! แม้ว่าตัวอย่างข้อมูลของคุณจะสั้นกว่า (และเป็นเอกสาร) แต่ฉันคิดว่าการเปลี่ยนชื่อโมดูลนั้นง่ายกว่าการมีโค้ดแฮ็กที่อาจทำให้ผู้คน (หรือตัวฉันเอง) สับสนในอนาคต
กิ่งไม้

15

วิธีเดียวที่จะแก้ปัญหานี้คือการจี้เครื่องจักรนำเข้าภายในด้วยตัวคุณเอง นี่ไม่ใช่เรื่องง่ายและเต็มไปด้วยอันตราย คุณควรหลีกเลี่ยงสัญญาณไฟรูปจอกเพราะอันตรายเกินไป

เปลี่ยนชื่อโมดูลของคุณแทน

หากคุณต้องการเรียนรู้วิธีการจี้เครื่องจักรนำเข้าภายในคุณจะต้องไปที่นี่เพื่อดูวิธีดำเนินการ:

บางครั้งก็มีเหตุผลที่ดีที่จะต้องเผชิญกับอันตรายนี้ เหตุผลที่คุณให้ไม่ได้อยู่ในหมู่พวกเขา เปลี่ยนชื่อโมดูลของคุณ

หากคุณใช้เส้นทางที่เต็มไปด้วยอันตรายปัญหาหนึ่งที่คุณจะพบคือเมื่อคุณโหลดโมดูลมันจะลงท้ายด้วย 'ชื่อทางการ' เพื่อให้ Python หลีกเลี่ยงไม่ต้องแยกวิเคราะห์เนื้อหาของโมดูลนั้นอีกเลย การแมป 'ชื่อทางการ' ของโมดูลกับอ็อบเจ็กต์โมดูลนั้นสามารถพบได้ในsys.modules.

ซึ่งหมายความว่าหากคุณimport calendarอยู่ในที่เดียวโมดูลใดก็ตามที่นำเข้าจะถูกคิดว่าเป็นโมดูลที่มีชื่ออย่างเป็นทางการcalendarและความพยายามอื่น ๆ ทั้งหมดไปยังimport calendarที่อื่น ๆ รวมถึงในโค้ดอื่น ๆ ที่เป็นส่วนหนึ่งของไลบรารี Python หลักก็จะได้ปฏิทินนั้น

อาจเป็นไปได้ที่จะออกแบบผู้นำเข้าของลูกค้าโดยใช้โมดูลอิมพิทิลใน Python 2.x ซึ่งทำให้โมดูลที่โหลดจากพา ธ บางอย่างเพื่อค้นหาโมดูลที่พวกเขากำลังนำเข้าในสิ่งอื่นที่ไม่ใช่sys.modulesครั้งแรกหรืออะไรทำนองนั้น แต่นั่นเป็นสิ่งที่ยุ่งยากมากที่ต้องทำและมันจะไม่ทำงานใน Python 3.x อยู่ดี

มีสิ่งที่น่าเกลียดและน่าสยดสยองอย่างยิ่งที่คุณสามารถทำได้โดยไม่ต้องเกี่ยวข้องกับกลไกการนำเข้า นี่เป็นสิ่งที่คุณไม่ควรทำ แต่ก็น่าจะได้ผล จะเปลี่ยนcalendarโมดูลของคุณให้เป็นไฮบริดของโมดูลปฏิทินระบบและโมดูลปฏิทินของคุณ ขอขอบคุณที่โบอาส 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
Boaz Yaniv

ซึ่งเข้ากันได้กับ Python 3 อย่างสมบูรณ์แบบ และไม่ขนที่จะใช้เลย แต่คุณควรทราบไว้เสมอว่าโค้ดที่อาศัย python จัดการพา ธ ด้วยวิธีเดียวหรือค้นหาโมดูลตามลำดับนั้นอาจแตกไม่ช้าก็เร็ว
Boaz Yaniv

1
ถูกต้อง แต่ในกรณีที่แยกได้ (การชนกันของชื่อโมดูล) การเชื่อมต่อกลไกการนำเข้าถือเป็นการใช้งานมากเกินไป และเนื่องจากมันมีขนดกและเข้ากันไม่ได้จึงควรปล่อยไว้ตามลำพัง
Boaz Yaniv

1
@jspacek ไม่ดีจนถึงตอนนี้ แต่การชนกันจะเกิดขึ้นเฉพาะเมื่อใช้ดีบักเกอร์ของ PyDev ไม่ใช่การใช้งานปกติ และตรวจสอบรหัสล่าสุด (URL ใน github) เนื่องจากมีการเปลี่ยนแปลงเล็กน้อยจากคำตอบด้านบน
MestreLion

1
@jspacek: มันเป็นเกมไม่ใช่ไลบรารีดังนั้นในกรณีของฉันความเข้ากันได้แบบย้อนหลังจึงไม่น่ากังวลเลย และการชนกันของเนมสเปซจะเกิดขึ้นเฉพาะเมื่อใช้งานผ่าน PyDev IDE (ซึ่งใช้codeโมดูล std ของ Python ) ซึ่งหมายความว่ามีนักพัฒนาเพียงบางส่วนเท่านั้นที่อาจมีปัญหากับ "การรวมแฮ็ก" นี้ ผู้ใช้จะไม่ได้รับผลกระทบเลย
MestreLion

1

ฉันต้องการเสนอเวอร์ชันของฉันซึ่งเป็นการผสมผสานระหว่างโซลูชันของ Boaz Yaniv และ Omnifarious มันจะนำเข้าเวอร์ชันระบบของโมดูลโดยมีข้อแตกต่างหลักสองประการจากคำตอบก่อนหน้านี้:

  • รองรับสัญกรณ์ 'dot' เช่น package.module
  • เป็นการแทนที่แบบดรอปอินสำหรับคำสั่งนำเข้าในโมดูลระบบซึ่งหมายความว่าคุณต้องแทนที่บรรทัดนั้นหนึ่งบรรทัดและหากมีการเรียกไปยังโมดูลอยู่แล้วก็จะทำงานได้ตามที่เป็นอยู่

วางสิ่งนี้ไว้ในที่ที่สามารถเข้าถึงได้เพื่อให้คุณสามารถเรียกใช้ได้ (ฉันมีของฉันอยู่ในไฟล์ __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

@Omnifarious: นั่นเป็นปัญหาที่แตกต่างซึ่งคุณสามารถแก้ไขได้ด้วยโมดูลที่สามที่ทำจากการนำเข้าปฏิทิน *
linuts

ไม่สิ่งนี้อาจใช้ไม่ได้เนื่องจาก python เก็บชื่อโมดูลไว้sys.modulesและจะไม่นำเข้าโมดูลที่มีชื่อเดียวกันอีก
Boaz Yaniv
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.