Рубрики
Без рубрики

Модули и импорт

Автор оригинала: Doug Hellmann.

Большинство программ Python представляют собой комбинацию нескольких модулей, которые импортирует главное приложение. Независимо от того, используете ли вы функции стандартной библиотеки или организуете пользовательский код в отдельных файлах, чтобы упростить обслуживание, понимание и управление зависимостями для программы является важным аспектом разработки. sys включает информацию о модулях, доступных приложению, либо как встроенных, либо после импорта. Он также определяет ловушки для переопределения стандартного поведения импорта для особых случаев.

Импортированные модули

sys.modules – это словарь, сопоставляющий имена импортированных модулей с объектом модуля, содержащим код.

sys_modules.py

import sys
import textwrap

names  sorted(sys.modules.keys())
name_text  ', '.join(names)

print(textwrap.fill(name_text, width64))

Содержимое sys.modules изменяется по мере импорта новых модулей.

$ python3 sys_modules.py

__main__, _abc, _bootlocale, _codecs, _collections,
_collections_abc, _frozen_importlib, _frozen_importlib_external,
_functools, _heapq, _imp, _io, _locale, _operator, _signal,
_sre, _stat, _thread, _warnings, _weakref, abc, builtins,
codecs, collections, contextlib, copyreg, encodings,
encodings.aliases, encodings.latin_1, encodings.utf_8, enum,
functools, genericpath, heapq, importlib, importlib._bootstrap,
importlib._bootstrap_external, importlib.abc,
importlib.machinery, importlib.util, io, itertools, keyword,
marshal, operator, os, os.path, posix, posixpath, re, reprlib,
site, sphinxcontrib, sre_compile, sre_constants, sre_parse,
stat, sys, textwrap, types, warnings, zipimport

Встроенные модули

Интерпретатор Python может быть скомпилирован с некоторыми встроенными модулями C, поэтому их не нужно распространять как отдельные разделяемые библиотеки. Эти модули не отображаются в списке импортированных модулей, управляемых в sys.modules , поскольку они не были импортированы технически. Единственный способ найти доступные встроенные модули – использовать sys.builtin_module_names .

sys_builtins.py

import sys
import textwrap

name_text  ', '.join(sorted(sys.builtin_module_names))

print(textwrap.fill(name_text, width64))

Вывод этого сценария будет отличаться, особенно если он запускается с настраиваемой версией интерпретатора. Этот вывод был создан с использованием копии интерпретатора, установленного из стандартного установщика python.org для OS X.

$ python3 sys_builtins.py

_abc, _ast, _codecs, _collections, _functools, _imp, _io,
_locale, _operator, _signal, _sre, _stat, _string, _symtable,
_thread, _tracemalloc, _warnings, _weakref, atexit, builtins,
errno, faulthandler, gc, itertools, marshal, posix, pwd, sys,
time, xxsubtype, zipimport

Смотрите также

Путь импорта

Путь поиска модулей управляется как список Python, сохраненный в sys.path . Содержимое пути по умолчанию включает каталог сценария, использованного для запуска приложения, и текущий рабочий каталог.

sys_path_show.py

import sys

for d in sys.path:
    print(d)

Первый каталог в пути поиска является домом для самого примера сценария. За этим следует ряд путей, зависящих от платформы, по которым могут быть установлены скомпилированные модули расширения (написанные на C), а затем глобальный каталог site-packages отображается последним.

$ python3 sys_path_show.py

/Users/dhellmann/Documents/PyMOTW/pymotw-3/source/sys
.../python35.zip
.../lib/python3.5
.../lib/python3.5/plat-darwin
.../python3.5/lib-dynload
.../lib/python3.5/site-packages

Список путей поиска импорта можно изменить перед запуском интерпретатора, установив для переменной оболочки PYTHONPATH список каталогов, разделенных двоеточиями.

$
> python3 sys_path_show.py

/Users/dhellmann/Documents/PyMOTW/pymotw-3/source/sys
/my/private/site-packages
/my/shared/site-packages
.../python35.zip
.../lib/python3.5
.../lib/python3.5/plat-darwin
.../python3.5/lib-dynload
.../lib/python3.5/site-packages

Программа также может изменять свой путь, добавляя элементы непосредственно в sys.path .

sys_path_modify.py

import importlib
import os
import sys

base_dir  os.path.dirname(__file__) or '.'
print('Base directory:', base_dir)

# Insert the package_dir_a directory at the front of the path.
package_dir_a  os.path.join(base_dir, 'package_dir_a')
sys.path.insert(0, package_dir_a)

# Import the example module
import example
print('Imported example from:', example.__file__)
print('  ', example.DATA)

# Make package_dir_b the first directory in the search path
package_dir_b  os.path.join(base_dir, 'package_dir_b')
sys.path.insert(0, package_dir_b)

# Reload the module to get the other version
importlib.reload(example)
print('Reloaded example from:', example.__file__)
print('  ', example.DATA)

При повторной загрузке импортированного модуля файл повторно импортируется, а для хранения результатов используется тот же объект module . Изменение пути между начальным импортом и вызовом reload () означает, что другой модуль может быть загружен во второй раз.

$ python3 sys_path_modify.py

Base directory: .
Imported example from: ./package_dir_a/example.py
   This is example A
Reloaded example from: ./package_dir_b/example.py
   This is example B

Пользовательские импортеры

Изменение пути поиска позволяет программисту контролировать поиск стандартных модулей Python. Но что, если программе необходимо импортировать код откуда-нибудь, кроме обычных файлов .py или .pyc в файловой системе? PEP 302 решает эту проблему, предлагая идею перехватчиков импорта. , что может отследить попытку найти модуль на пути поиска и принять альтернативные меры для загрузки кода из другого места или применения к нему предварительной обработки.

Пользовательские импортеры реализуются в два отдельных этапа. Finder отвечает за обнаружение модуля и предоставляет загрузчик для управления фактическим импортом. Пользовательские средства поиска модулей добавляются путем добавления фабрики в список sys.path_hooks . При импорте каждая часть пути передается искателю до тех пор, пока один из них не заявит о поддержке (не вызывая ImportError ). Затем этот искатель отвечает за поиск в хранилище данных, представленном его записью пути, для именованных модулей.

sys_path_hooks_noisy.py

import sys


class NoisyImportFinder:

    PATH_TRIGGER  'NoisyImportFinder_PATH_TRIGGER'

    def __init__(self, path_entry):
        print('Checking {}:'.format(path_entry), end' ')
        if path_entry  self.PATH_TRIGGER:
            print('wrong finder')
            raise ImportError()
        else:
            print('works')
        return

    def find_module(self, fullname, pathNone):
        print('Looking for {!r}'.format(fullname))
        return None


sys.path_hooks.append(NoisyImportFinder)

for hook in sys.path_hooks:
    print('Path hook: {}'.format(hook))

sys.path.insert(0, NoisyImportFinder.PATH_TRIGGER)

try:
    print('importing target_module')
    import target_module
except Exception as e:
    print('Import failed:', e)

Этот пример показывает, как создаются и запрашиваются средства поиска. NoisyImportFinder вызывает ImportError при создании экземпляра с записью пути, которая не соответствует его специальному значению триггера, которое, очевидно, не является реальным путем в файловой системе. Этот тест не позволяет NoisyImportFinder нарушать импорт реальных модулей.

$ python3 sys_path_hooks_noisy.py

Path hook: 
Path hook: .path_hook_for_FileFinder at
0x101afb6a8>
Path hook: 
importing target_module
Checking NoisyImportFinder_PATH_TRIGGER: works
Looking for 'target_module'
Import failed: No module named 'target_module'

Импорт с полки

Когда средство поиска обнаруживает модуль, оно отвечает за возврат загрузчика , способного импортировать этот модуль. В этом примере показан настраиваемый импортер, который сохраняет содержимое своего модуля в базе данных, созданной с помощью shelve.

Сначала используется сценарий для заполнения полки пакетом, содержащим субмодуль и субпакет.

sys_shelve_importer_create.py

import shelve
import os

filename  '/tmp/pymotw_import_example.shelve'
if os.path.exists(filename + '.db'):
    os.unlink(filename + '.db')
with shelve.open(filename) as db:
    db['data:README']  b"""

package README


This is the README for ``package``.
"""
    db['package.__init__']  b"""
print('package imported')
message = 'This message is in package.__init__'
"""
    db['package.module1']  b"""
print('package.module1 imported')
message = 'This message is in package.module1'
"""
    db['package.subpackage.__init__']  b"""
print('package.subpackage imported')
message = 'This message is in package.subpackage.__init__'
"""
    db['package.subpackage.module2']  b"""
print('package.subpackage.module2 imported')
message = 'This message is in package.subpackage.module2'
"""
    db['package.with_error']  b"""
print('package.with_error being imported')
raise ValueError('raising exception to break import')
"""
    print('Created {} with:'.format(filename))
    for key in sorted(db.keys()):
        print('  ', key)

Настоящий сценарий упаковки будет читать содержимое из файловой системы, но для такого простого примера, как этот, достаточно использовать жестко заданные значения.

$ python3 sys_shelve_importer_create.py

Created /tmp/pymotw_import_example.shelve with:
   data:README
   package.__init__
   package.module1
   package.subpackage.__init__
   package.subpackage.module2
   package.with_error

Пользовательский импортер должен предоставить классы поиска и загрузчика, которые знают, как искать на полке источник модуля или пакета.

sys_shelve_importer.py

import imp
import os
import shelve
import sys


def _mk_init_name(fullname):
    """Return the name of the __init__ module
    for a given package name.
    """
    if fullname.endswith('.__init__'):
        return fullname
    return fullname + '.__init__'


def _get_key_name(fullname, db):
    """Look in an open shelf for fullname or
    fullname.__init__, return the name found.
    """
    if fullname in db:
        return fullname
    init_name  _mk_init_name(fullname)
    if init_name in db:
        return init_name
    return None


class ShelveFinder:
    """Find modules collected in a shelve archive."""

    _maybe_recursing  False

    def __init__(self, path_entry):
        # Loading shelve causes an import recursive loop when it
        # imports dbm, and we know we are not going to load the
        # module # being imported, so when we seem to be
        # recursing just ignore the request so another finder
        # will be used.
        if ShelveFinder._maybe_recursing:
            raise ImportError
        try:
            # Test the path_entry to see if it is a valid shelf
            try:
                ShelveFinder._maybe_recursing  True
                with shelve.open(path_entry, 'r'):
                    pass
            finally:
                ShelveFinder._maybe_recursing  False
        except Exception as e:
            print('shelf could not import from {}: {}'.format(
                path_entry, e))
            raise
        else:
            print('shelf added to import path:', path_entry)
            self.path_entry  path_entry
        return

    def __str__(self):
        return '<{} for {!r}>'.format(self.__class__.__name__,
                                      self.path_entry)

    def find_module(self, fullname, pathNone):
        path  path or self.path_entry
        print('\nlooking for {!r}\n  in {}'.format(
            fullname, path))
        with shelve.open(self.path_entry, 'r') as db:
            key_name  _get_key_name(fullname, db)
            if key_name:
                print('  found it as {}'.format(key_name))
                return ShelveLoader(path)
        print('  not found')
        return None


class ShelveLoader:
    """Load source for modules from shelve databases."""

    def __init__(self, path_entry):
        self.path_entry  path_entry
        return

    def _get_filename(self, fullname):
        # Make up a fake filename that starts with the path entry
        # so pkgutil.get_data() works correctly.
        return os.path.join(self.path_entry, fullname)

    def get_source(self, fullname):
        print('loading source for {!r} from shelf'.format(
            fullname))
        try:
            with shelve.open(self.path_entry, 'r') as db:
                key_name  _get_key_name(fullname, db)
                if key_name:
                    return db[key_name]
                raise ImportError(
                    'could not find source for {}'.format(
                        fullname)
                )
        except Exception as e:
            print('could not load source:', e)
            raise ImportError(str(e))

    def get_code(self, fullname):
        source  self.get_source(fullname)
        print('compiling code for {!r}'.format(fullname))
        return compile(source, self._get_filename(fullname),
                       'exec', dont_inheritTrue)

    def get_data(self, path):
        print('looking for data\n  in {}\n  for {!r}'.format(
            self.path_entry, path))
        if not path.startswith(self.path_entry):
            raise IOError
        path  path[len(self.path_entry) + 1:]
        key_name  'data:' + path
        try:
            with shelve.open(self.path_entry, 'r') as db:
                return db[key_name]
        except Exception:
            # Convert all errors to IOError
            raise IOError()

    def is_package(self, fullname):
        init_name  _mk_init_name(fullname)
        with shelve.open(self.path_entry, 'r') as db:
            return init_name in db

    def load_module(self, fullname):
        source  self.get_source(fullname)

        if fullname in sys.modules:
            print('reusing module from import of {!r}'.format(
                fullname))
            mod  sys.modules[fullname]
        else:
            print('creating a new module object for {!r}'.format(
                fullname))
            mod  sys.modules.setdefault(
                fullname,
                imp.new_module(fullname)
            )

        # Set a few properties required by PEP 302
        mod.__file__  self._get_filename(fullname)
        mod.__name__  fullname
        mod.__path__  self.path_entry
        mod.__loader__  self
        # PEP-366 specifies that package's set __package__ to
        # their name, and modules have it set to their parent
        # package (if any).
        if self.is_package(fullname):
            mod.__package__  fullname
        else:
            mod.__package__  '.'.join(fullname.split('.')[:-1])

        if self.is_package(fullname):
            print('adding path for package')
            # Set __path__ for packages
            # so we can find the sub-modules.
            mod.__path__  [self.path_entry]
        else:
            print('imported as regular module')

        print('execing source...')
        exec(source, mod.__dict__)
        print('done')
        return mod

Теперь ShelveFinder и ShelveLoader можно использовать для импорта кода с полки. Например, импорт только что созданного пакета :

sys_shelve_importer_package.py

import sys
import sys_shelve_importer


def show_module_details(module):
    print('  message    :', module.message)
    print('  __name__   :', module.__name__)
    print('  __package__:', module.__package__)
    print('  __file__   :', module.__file__)
    print('  __path__   :', module.__path__)
    print('  __loader__ :', module.__loader__)


filename  '/tmp/pymotw_import_example.shelve'
sys.path_hooks.append(sys_shelve_importer.ShelveFinder)
sys.path.insert(0, filename)

print('Import of "package":')
import package

print()
print('Examine package details:')
show_module_details(package)

print()
print('Global settings:')
print('sys.modules entry:')
print(sys.modules['package'])

Полка добавляется к пути импорта при первом импорте после изменения пути. Средство поиска распознает полку и возвращает загрузчик, который используется для всего импорта с этой полки. Первоначальный импорт на уровне пакета создает новый объект модуля, а затем использует exec для запуска источника, загруженного с полки. Он использует новый модуль в качестве пространства имен, так что имена, определенные в источнике, сохраняются как атрибуты уровня модуля.

$ python3 sys_shelve_importer_package.py

Import of "package":
shelf added to import path: /tmp/pymotw_import_example.shelve

looking for 'package'
  in /tmp/pymotw_import_example.shelve
  found it as package.__init__
loading source for 'package' from shelf
creating a new module object for 'package'
adding path for package
execing source...
package imported
done

Examine package details:
  message    : This message is in package.__init__
  __name__   : package
  __package__: package
  __file__   : /tmp/pymotw_import_example.shelve/package
  __path__   : ['/tmp/pymotw_import_example.shelve']
  __loader__ : 

Global settings:
sys.modules entry:
)>

Импорт пользовательского пакета

Таким же образом происходит загрузка других модулей и подпакетов.

sys_shelve_importer_module.py

import sys
import sys_shelve_importer


def show_module_details(module):
    print('  message    :', module.message)
    print('  __name__   :', module.__name__)
    print('  __package__:', module.__package__)
    print('  __file__   :', module.__file__)
    print('  __path__   :', module.__path__)
    print('  __loader__ :', module.__loader__)


filename  '/tmp/pymotw_import_example.shelve'
sys.path_hooks.append(sys_shelve_importer.ShelveFinder)
sys.path.insert(0, filename)

print('Import of "package.module1":')
import package.module1

print()
print('Examine package.module1 details:')
show_module_details(package.module1)

print()
print('Import of "package.subpackage.module2":')
import package.subpackage.module2

print()
print('Examine package.subpackage.module2 details:')
show_module_details(package.subpackage.module2)

Средство поиска получает полное имя загружаемого модуля, разделенное точками, и возвращает ShelveLoader , настроенный для загрузки модулей из записи пути, указывающей на файл полки. Полное имя модуля передается в метод загрузчика load_module () , который создает и возвращает экземпляр module .

$ python3 sys_shelve_importer_module.py

Import of "package.module1":
shelf added to import path: /tmp/pymotw_import_example.shelve

looking for 'package'
  in /tmp/pymotw_import_example.shelve
  found it as package.__init__
loading source for 'package' from shelf
creating a new module object for 'package'
adding path for package
execing source...
package imported
done

looking for 'package.module1'
  in /tmp/pymotw_import_example.shelve
  found it as package.module1
loading source for 'package.module1' from shelf
creating a new module object for 'package.module1'
imported as regular module
execing source...
package.module1 imported
done

Examine package.module1 details:
  message    : This message is in package.module1
  __name__   : package.module1
  __package__: package
  __file__   : /tmp/pymotw_import_example.shelve/package.module1
  __path__   : /tmp/pymotw_import_example.shelve
  __loader__ : 

Import of "package.subpackage.module2":

looking for 'package.subpackage'
  in /tmp/pymotw_import_example.shelve
  found it as package.subpackage.__init__
loading source for 'package.subpackage' from shelf
creating a new module object for 'package.subpackage'
adding path for package
execing source...
package.subpackage imported
done

looking for 'package.subpackage.module2'
  in /tmp/pymotw_import_example.shelve
  found it as package.subpackage.module2
loading source for 'package.subpackage.module2' from shelf
creating a new module object for 'package.subpackage.module2'
imported as regular module
execing source...
package.subpackage.module2 imported
done

Examine package.subpackage.module2 details:
  message    : This message is in package.subpackage.module2
  __name__   : package.subpackage.module2
  __package__: package.subpackage
  __file__   :
/tmp/pymotw_import_example.shelve/package.subpackage.module2
  __path__   : /tmp/pymotw_import_example.shelve
  __loader__ : 

Перезагрузка модулей в пользовательском импортере

Перезагрузка модуля осуществляется немного иначе. Вместо создания нового объекта модуля повторно используется существующий объект.

sys_shelve_importer_reload.py

import importlib
import sys
import sys_shelve_importer

filename  '/tmp/pymotw_import_example.shelve'
sys.path_hooks.append(sys_shelve_importer.ShelveFinder)
sys.path.insert(0, filename)

print('First import of "package":')
import package

print()
print('Reloading "package":')
importlib.reload(package)

При повторном использовании того же объекта существующие ссылки на модуль сохраняются, даже если определения класса или функции изменяются при перезагрузке.

$ python3 sys_shelve_importer_reload.py

First import of "package":
shelf added to import path: /tmp/pymotw_import_example.shelve

looking for 'package'
  in /tmp/pymotw_import_example.shelve
  found it as package.__init__
loading source for 'package' from shelf
creating a new module object for 'package'
adding path for package
execing source...
package imported
done

Reloading "package":

looking for 'package'
  in /tmp/pymotw_import_example.shelve
  found it as package.__init__
loading source for 'package' from shelf
reusing module from import of 'package'
adding path for package
execing source...
package imported
done

Обработка ошибок импорта

Если модуль не может быть обнаружен никаким средством поиска, ImportError вызывается основным кодом импорта.

sys_shelve_importer_missing.py

import sys
import sys_shelve_importer

filename  '/tmp/pymotw_import_example.shelve'
sys.path_hooks.append(sys_shelve_importer.ShelveFinder)
sys.path.insert(0, filename)

try:
    import package.module3
except ImportError as e:
    print('Failed to import:', e)

Распространяются другие ошибки при импорте.

$ python3 sys_shelve_importer_missing.py

shelf added to import path: /tmp/pymotw_import_example.shelve

looking for 'package'
  in /tmp/pymotw_import_example.shelve
  found it as package.__init__
loading source for 'package' from shelf
creating a new module object for 'package'
adding path for package
execing source...
package imported
done

looking for 'package.module3'
  in /tmp/pymotw_import_example.shelve
  not found
Failed to import: No module named 'package.module3'

Данные пакета

В дополнение к определению API для загрузки исполняемого кода Python, PEP 302 определяет дополнительный API для извлечения данных пакета, предназначенных для распространения файлов данных, документации и других ресурсов, не связанных с кодом, используемых пакетом. Реализуя get_data () , загрузчик может позволить вызывающим приложениям поддерживать получение данных, связанных с пакетом, без учета того, как пакет фактически установлен (особенно без предположения, что пакет хранится как файлы в файле. система).

sys_shelve_importer_get_data.py

import sys
import sys_shelve_importer
import os
import pkgutil

filename  '/tmp/pymotw_import_example.shelve'
sys.path_hooks.append(sys_shelve_importer.ShelveFinder)
sys.path.insert(0, filename)

import package

readme_path  os.path.join(package.__path__[0], 'README')

readme  pkgutil.get_data('package', 'README')
# Equivalent to:
#  readme = package.__loader__.get_data(readme_path)
print(readme.decode('utf-8'))

foo_path  os.path.join(package.__path__[0], 'foo')
try:
    foo  pkgutil.get_data('package', 'foo')
    # Equivalent to:
    #  foo = package.__loader__.get_data(foo_path)
except IOError as err:
    print('ERROR: Could not load "foo"', err)
else:
    print(foo)

get_data () принимает путь на основе модуля или пакета, который владеет данными, и возвращает содержимое “файла” ресурса в виде байтовой строки или вызывает IOError , если ресурс не существует.

$ python3 sys_shelve_importer_get_data.py

shelf added to import path: /tmp/pymotw_import_example.shelve

looking for 'package'
  in /tmp/pymotw_import_example.shelve
  found it as package.__init__
loading source for 'package' from shelf
creating a new module object for 'package'
adding path for package
execing source...
package imported
done
looking for data
  in /tmp/pymotw_import_example.shelve
  for '/tmp/pymotw_import_example.shelve/README'

package README

This is the README for ``package``.

looking for data
  in /tmp/pymotw_import_example.shelve
  for '/tmp/pymotw_import_example.shelve/foo'
ERROR: Could not load "foo"

Смотрите также

  • pkgutil – включает get_data () для получения данных из пакета.

Кэш импортера

Поиск по всем перехватчикам каждый раз при импорте модуля может стать дорогостоящим. Для экономии времени sys.path_importer_cache поддерживается как отображение между записью пути и загрузчиком, который может использовать это значение для поиска модулей.

sys_path_importer_cache.py

import os
import sys

prefix  os.path.abspath(sys.prefix)

print('PATH:')
for name in sys.path:
    name  name.replace(prefix, '...')
    print(' ', name)

print()
print('IMPORTERS:')
for name, cache_value in sys.path_importer_cache.items():
    if '..' in name:
        name  os.path.abspath(name)
    name  name.replace(prefix, '...')
    print('  {}: {!r}'.format(name, cache_value))

FileFinder используется для расположения путей, найденных в файловой системе. Местоположения на пути, не поддерживаемые никаким средством поиска, связаны с None , поскольку они не могут использоваться для импорта модулей. Вывод ниже был усечен из-за ограничений форматирования.

$ python3 sys_path_importer_cache.py

PATH:
  /Users/dhellmann/Documents/PyMOTW/Python3/pymotw-3/source/sys
  .../lib/python35.zip
  .../lib/python3.5
  .../lib/python3.5/plat-darwin
  .../lib/python3.5/lib-dynload
  .../lib/python3.5/site-packages

IMPORTERS:
  sys_path_importer_cache.py: None
  .../lib/python3.5/encodings: FileFinder(
  '.../lib/python3.5/encodings')
  .../lib/python3.5/lib-dynload: FileFinder(
  '.../lib/python3.5/lib-dynload')
  .../lib/python3.5/lib-dynload: FileFinder(
  '.../lib/python3.5/lib-dynload')
  .../lib/python3.5/site-packages: FileFinder(
  '.../lib/python3.5/site-packages')
  .../lib/python3.5: FileFinder(
  '.../lib/python3.5/')
  .../lib/python3.5/plat-darwin: FileFinder(
  '.../lib/python3.5/plat-darwin')
  .../lib/python3.5: FileFinder(
  '.../lib/python3.5')
  .../lib/python35.zip: None
  .../lib/python3.5/plat-darwin: FileFinder(
  '.../lib/python3.5/plat-darwin')

Мета Путь

sys.meta_path дополнительно расширяет источники потенциального импорта, позволяя искать средство поиска перед сканированием обычного sys.path . API для поисковика на мета-пути такой же, как и для обычного пути. Разница в том, что метаискатель не ограничен одной записью в sys.path – он может искать где угодно.

sys_meta_path.py

import sys
import types


class NoisyMetaImportFinder:

    def __init__(self, prefix):
        print('Creating NoisyMetaImportFinder for {}'.format(
            prefix))
        self.prefix  prefix
        return

    def find_module(self, fullname, pathNone):
        print('looking for {!r} with path {!r}'.format(
            fullname, path))
        name_parts  fullname.split('.')
        if name_parts and name_parts[0]  self.prefix:
            print(' ... found prefix, returning loader')
            return NoisyMetaImportLoader(path)
        else:
            print(' ... not the right prefix, cannot load')
        return None


class NoisyMetaImportLoader:

    def __init__(self, path_entry):
        self.path_entry  path_entry
        return

    def load_module(self, fullname):
        print('loading {}'.format(fullname))
        if fullname in sys.modules:
            mod  sys.modules[fullname]
        else:
            mod  sys.modules.setdefault(
                fullname,
                types.ModuleType(fullname))

        # Set a few properties required by PEP 302
        mod.__file__  fullname
        mod.__name__  fullname
        # always looks like a package
        mod.__path__  ['path-entry-goes-here']
        mod.__loader__  self
        mod.__package__  '.'.join(fullname.split('.')[:-1])

        return mod


# Install the meta-path finder
sys.meta_path.append(NoisyMetaImportFinder('foo'))

# Import some modules that are "found" by the meta-path finder
print()
import foo

print()
import foo.bar

# Import a module that is not found
print()
try:
    import bar
except ImportError as e:
    pass

Каждый искатель на мета-пути опрашивается перед поиском в sys.path , поэтому всегда есть возможность иметь центральный импортер для загрузки модулей без явного изменения sys.path . После того, как модуль «найден», API загрузчика работает так же, как и для обычных загрузчиков (хотя этот пример для простоты усечен).

$ python3 sys_meta_path.py

Creating NoisyMetaImportFinder for foo

looking for 'foo' with path None
 ... found prefix, returning loader
loading foo

looking for 'foo.bar' with path ['path-entry-goes-here']
 ... found prefix, returning loader
loading foo.bar

looking for 'bar' with path None
 ... not the right prefix, cannot load

Смотрите также