Nhập từ thư viện nội trang khi tồn tại mô-đun có cùng tên


121

Tình huống: - Có một mô-đun trong project_folder của tôi được gọi là lịch - Tôi muốn sử dụng lớp Lịch tích hợp từ các thư viện Python - Khi tôi sử dụng từ Lịch nhập lịch, nó phàn nàn vì nó đang cố tải từ mô-đun của tôi.

Tôi đã thực hiện một vài tìm kiếm và dường như tôi không thể tìm ra giải pháp cho vấn đề của mình.

Bất kỳ ý tưởng nào mà không cần phải đổi tên mô-đun của tôi?


24
Cách tốt nhất là không đặt tên mô-đun để ẩn mô-đun nội trang.
the_drow

3
Giải pháp là "chọn một tên khác". Cách tiếp cận không đổi tên của bạn là một ý tưởng tồi. Tại sao bạn không thể đổi tên mô-đun của mình? Có gì sai khi đổi tên?
S.Lott

Thật. Đó là bởi vì không có câu trả lời tốt cho câu hỏi này mà việc làm bóng các mô-đun stdlib rất không được khuyến khích.
ncoghlan

Tôi đã tránh sử dụng cùng một tên mô-đun vì các giải pháp có vẻ rắc rối hơn đáng giá. Cảm ơn!
cành cây

9
@the_drow Lời khuyên này không quy mô, thuần túy và đơn giản. PEP328 dễ dàng xác nhận điều này.
Konrad Rudolph

Câu trả lời:


4

Giải pháp được chấp nhận chứa một phương pháp hiện không được dùng nữa.

Tài liệu importlib ở đây đưa ra một ví dụ điển hình về cách thích hợp hơn để tải mô-đun trực tiếp từ đường dẫn tệp cho 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)

Vì vậy, bạn có thể tải bất kỳ tệp .py nào từ một đường dẫn và đặt tên mô-đun thành bất kỳ tên nào bạn muốn. Vì vậy, chỉ cần điều chỉnh thành module_namebất kỳ tên tùy chỉnh nào bạn muốn mô-đun có khi nhập.

Để tải một gói thay vì một tệp, file_pathphải là đường dẫn đến thư mục gốc của gói__init__.py


Hoạt động như một sự quyến rũ ... Được sử dụng để kiểm tra trong khi phát triển thư viện, để các bài kiểm tra của tôi luôn sử dụng phiên bản đang phát triển chứ không phải phiên bản đã xuất bản (và đã cài đặt). Trong cửa sổ 10 tôi đã phải viết đường dẫn đến mô-đun của tôi như thế này: file_path=r"C:\Users\My User\My Path\Module File.py". Sau đó tôi gọi là module_namegiống như các mô-đun phát hành để tôi có kịch bản làm việc đầy đủ mà, cởi đoạn này, coud được sử dụng trên chiếc khác
Luke Savefrogs

141

Thay đổi tên của mô-đun của bạn là không cần thiết. Thay vào đó, bạn có thể sử dụng Absolute_import để thay đổi hành vi nhập. Ví dụ: với stem / socket.py tôi nhập mô-đun socket như sau:

from __future__ import absolute_import
import socket

Điều này chỉ hoạt động với Python 2.5 trở lên; nó cho phép hành vi là mặc định trong Python 3.0 trở lên. Pylint sẽ phàn nàn về mã nhưng nó hoàn toàn hợp lệ.


4
Đây có vẻ là câu trả lời chính xác cho tôi. Xem thay đổi 2.5 hoặc PEP328 để biết thêm.
Pieter Ennes,

5
Đây là giải pháp chính xác. Thật không may, nó không hoạt động khi mã từ bên trong gói được khởi chạy vì khi đó gói không được nhận dạng như vậy và đường dẫn cục bộ được thêm vào trước PYTHONPATH. Một câu hỏi khác chỉ ra cách giải quyết điều đó.
Konrad Rudolph

5
Đây là giải pháp. Tôi đã kiểm tra Python 2.7.6 và điều này là bắt buộc, nó vẫn không phải là mặc định.
Havok

3
Thật vậy: Phiên bản python đầu tiên mà hành vi này là mặc định là 3.0, theo docs.python.org/2/library/__future__.html
cái tên nhầm lẫn

1
Sau đó, không đặt tên mô-đun chính của bạn mô-đun xung đột với mô-đun nội trang.
Antti Haapala

38

Trên thực tế, việc giải quyết vấn đề này khá dễ dàng, nhưng việc triển khai sẽ luôn hơi mong manh, vì nó phụ thuộc vào nội bộ của cơ chế nhập python và chúng có thể thay đổi trong các phiên bản trong tương lai.

(đoạn mã sau cho biết cách tải cả mô-đun cục bộ và không cục bộ và cách chúng có thể cùng tồn tại)

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

Giải pháp tốt nhất, nếu có thể, là tránh đặt tên mô-đun của bạn trùng tên với tên thư viện chuẩn hoặc tên mô-đun tích hợp sẵn.


Điều này sẽ tương tác như thế nào sys.modulesvà các nỗ lực tiếp theo để tải mô-đun cục bộ?
Omnifarious

@Omnifarious: Nó sẽ thêm mô-đun vào sys.modules với tên của nó, điều này sẽ ngăn việc tải mô-đun cục bộ. Bạn luôn có thể sử dụng tên tùy chỉnh để tránh điều đó.
Boaz Yaniv

@Boaz Yaniv: Bạn nên sử dụng tên tùy chỉnh cho lịch địa phương, không phải tên tiêu chuẩn. Các mô-đun Python khác có thể cố gắng nhập mô-đun chuẩn. Và nếu bạn làm điều đó, những gì bạn đạt được với điều này về cơ bản là đổi tên mô-đun cục bộ mà không cần phải đổi tên tệp.
Omnifarious

@Omnifarious: Bạn có thể làm theo một trong hai cách. Một số mã khác có thể cố gắng tải mô-đun cục bộ và gặp lỗi tương tự. Bạn sẽ phải thỏa hiệp và tùy thuộc vào bạn để quyết định mô-đun nào sẽ hỗ trợ.
Boaz Yaniv

2
Cảm ơn vì Boaz! Mặc dù đoạn mã của bạn ngắn hơn (và là tài liệu), tôi nghĩ rằng việc đổi tên mô-đun sẽ dễ dàng hơn so với việc có một số mã hack có thể khiến mọi người (hoặc chính tôi) nhầm lẫn trong tương lai.
cành cây

15

Cách duy nhất để giải quyết vấn đề này là tự mình chiếm đoạt máy móc nhập khẩu nội bộ. Điều này không dễ dàng, và đầy nguy hiểm. Bạn nên tránh đèn hiệu hình cái chén bằng mọi giá vì hiểm họa quá nguy hiểm.

Thay vào đó hãy đổi tên mô-đun của bạn.

Nếu bạn muốn học cách chiếm đoạt máy móc nhập khẩu nội bộ, đây là nơi bạn sẽ tìm hiểu cách thực hiện điều này:

Đôi khi có những lý do chính đáng để đi vào nguy cơ này. Lý do bạn đưa ra không nằm trong số đó. Đổi tên mô-đun của bạn.

Nếu bạn đi theo con đường nguy hiểm, một vấn đề bạn sẽ gặp phải là khi bạn tải một mô-đun, nó kết thúc bằng 'tên chính thức' để Python có thể tránh phải phân tích lại nội dung của mô-đun đó. Bạn có thể tìm thấy ánh xạ 'tên chính thức' của mô-đun với đối tượng mô-đun sys.modules.

Điều này có nghĩa là nếu bạn import calendarở một nơi, bất kỳ mô-đun nào được nhập sẽ được coi là mô-đun có tên chính thức calendarvà tất cả các nỗ lực khác đến import calendarbất kỳ nơi nào khác, bao gồm cả trong mã khác thuộc thư viện Python chính, sẽ nhận được lịch đó.

Có thể thiết kế một trình nhập khách hàng bằng cách sử dụng mô-đun imputil trong Python 2.x khiến các mô-đun được tải từ một số đường dẫn nhất định tìm kiếm các mô-đun mà họ đang nhập trong thứ gì đó khác với sys.modulesthứ nhất hoặc thứ gì đó tương tự. Nhưng đó là một điều cực kỳ khó thực hiện và nó sẽ không hoạt động trong Python 3.x.

Có một điều cực kỳ xấu xí và kinh khủng mà bạn có thể làm mà không liên quan đến việc nối cơ chế nhập. Đây là điều bạn có thể không nên làm, nhưng nó có thể sẽ hiệu quả. Nó biến calendarmô-đun của bạn thành mô-đun kết hợp giữa mô-đun lịch hệ thống và mô-đun lịch của bạn. Cảm ơn Boaz Yaniv cho bộ xương của chức năng tôi sử dụng . Đặt cái này ở đầu calendar.pytệp của bạn :

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 được coi là không dùng nữa. Bạn nên sử dụng mô-đun imp .
Boaz Yaniv

Nhân tiện, hoàn toàn tương thích với Python 3. Và không có lông để sử dụng ở tất cả. Nhưng bạn phải luôn lưu ý rằng mã dựa trên python xử lý đường dẫn theo một cách hoặc tìm kiếm các mô-đun theo thứ tự đó có thể sớm hoặc muộn.
Boaz Yaniv

1
Đúng, nhưng trong một trường hợp cá biệt như vậy (xung đột tên mô-đun), việc nối cơ chế nhập là một việc làm quá mức cần thiết. Và vì nó có nhiều lông và không tương thích, nên để yên thì tốt hơn.
Boaz Yaniv

1
@jspacek nope, cho đến nay rất tốt, nhưng va chạm sẽ chỉ xảy ra khi sử dụng trình gỡ lỗi của PyDev, không được sử dụng thường xuyên. Và hãy đảm bảo rằng bạn kiểm tra mã mới nhất (URL trong github), vì nó đã thay đổi một chút so với câu trả lời ở trên
MestreLion

1
@jspacek: Đó là một trò chơi, không phải một thư viện, vì vậy trong trường hợp của tôi, khả năng tương thích ngược không phải là vấn đề đáng lo ngại. Và xung đột không gian tên chỉ xảy ra khi sử dụng chạy qua PyDev IDE (sử dụng codemô-đun std của Python ), có nghĩa là chỉ một phần nhỏ các nhà phát triển có thể gặp bất kỳ vấn đề nào với "hack hợp nhất" này. Người dùng sẽ không bị ảnh hưởng gì cả.
MestreLion

1

Tôi muốn cung cấp phiên bản của mình, là sự kết hợp giữa giải pháp của Boaz Yaniv và Omnifarious. Nó sẽ nhập phiên bản hệ thống của một mô-đun, với hai điểm khác biệt chính so với các câu trả lời trước:

  • Hỗ trợ ký hiệu 'dấu chấm', ví dụ. package.module
  • Là một phần thay thế cho câu lệnh nhập trên mô-đun hệ thống, nghĩa là bạn chỉ cần thay thế một dòng đó và nếu đã có lệnh gọi đến mô-đun thì chúng sẽ hoạt động như hiện tại

Đặt cái này ở nơi nào đó có thể truy cập được để bạn có thể gọi nó (Tôi có cái của tôi trong tệp __init__.py của tôi):

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

Thí dụ

Tôi muốn nhập mysql.connection, nhưng tôi đã có một gói cục bộ có tên là mysql (tiện ích mysql chính thức). Vì vậy, để lấy trình kết nối từ gói mysql hệ thống, tôi đã thay thế cái này:

import mysql.connector

Với cái này:

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

Kết quả

# 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

Thay đổi đường dẫn nhập:

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

Điều này sẽ không hiệu quả bởi vì sau khi thực hiện việc này, sẽ không có cách nào để nhập mô-đun cục bộ mà không phải tự mình mày mò với máy móc nhập khẩu.
Omnifarious

@Omnifarious: đó là một vấn đề khác mà bạn có thể gặp phải với mô-đun thứ ba thực hiện từ nhập lịch *.
linuts

Không, điều này có thể sẽ không hoạt động vì python lưu tên mô-đun vào bộ nhớ cache sys.modulesvà nó sẽ không nhập lại mô-đun có cùng tên.
Boaz Yaniv
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.