На этом шаге мы рассмотрим несколько способов решения этой задачи.
Вы хотите настроить инструкцию импортирования Python, чтобы она могла прозрачно загружать модули с удаленного компьютера.
Во-первых, сразу отметим важность соблюдения правил безопасности. Идея, которая обсуждается в этом рецепте, станет ужасной без некого дополнительного слоя аутентификации и безопасности. Цель этого рецепта - глубоко погрузиться во внутренний механизм работы инструкции import. Если вы заставите этот рецепт работать и поймете внутреннюю механику, то получите серьезный фундамент знаний для приспособления import под практически любые нужды. Имея это в виду, давайте продолжим!
В сердце этого рецепта лежит желание расширить функциональность инструкции import. Есть несколько подходов к тому, как можно это сделать, но для наглядности начнем с создания такого каталога с кодом Python:
testcode/
spam.py
fib.py
grok/
__init__.py
blah.py
Содержимое этих файлов не имеет значения, но поместим в каждый из них несколько простых инструкций и функций, чтобы вы могли их протестировать и посмотреть на получающийся вывод при импортировании. Например:
# spam.py print("I'm spam") def hello(name): print('Hello %s' % name) # fib.py print("I'm fib") def fib(n): if n < 2: return 1 else: return fib(n-1) + fib(n-2) # grok/__init__.py print("I'm grok.__init__") # grok/blah.py print("I'm grok.blah")
Наша цель - разрешить удаленный доступ к этим файлам, как к модулям. Вероятно, наиболее простой способ сделать это - опубликовать их на веб-сервере. Просто зайдите в каталог testcode и запустите Python таким образом:
bash % cd testcode bash % python3 -m http.server 15000 Serving HTTP on 0.0.0.0 port 15000 ...
Оставим этот сервер в запущенном состоянии и отдельно запустим интерпретатор Python. Убедитесь, что вы имеете доступ к удаленным файлам, используя urllib. Например:
>>> from urllib.request import urlopen >>> u = urlopen('http://localhost:15000/fib.py') >>> data = u.read().decode('utf-8') >>> print(data) # fib.py print("I'm fib") def fib(n): if n < 2: return 1 else: return fib(n-1) + fib(n-2) >>>
Загрузка исходного кода с сервера - это база для оставшейся части этого рецепта. Если конкретно, то вместо ручного получения файла с исходным кодом с использованием urlopen() мы переделаем инструкцию import так, что она будет делать это "за кулисами".
Первый подход к загрузке удаленного модуля - создать явную функцию загрузки, которая будет этим заниматься. Например:
import imp import urllib.request import sys def load_module(url): u = urllib.request.urlopen(url) source = u.read().decode('utf-8') mod = sys.modules.setdefault(url, imp.new_module(url)) code = compile(source, url, 'exec') mod.__file__ = url mod.__package__ = '' exec(code, mod.__dict__) return mod
Эта функция просто загружает исходный код, компилирует его в объект кода, используя compile(), а затем выполняет словарь созданного объекта модуля. Вот как вы могли бы использовать эту функцию:
>>> fib = load_module('http://localhost:15000/fib.py') I'm fib >>> fib.fib(10) 89 >>> spam = load_module('http://localhost:15000/spam.py') I'm spam >>> spam.hello('Guido') Hello Guido >>> fib <module 'http://localhost:15000/fib.py' from 'http://localhost:15000/fib.py'> >>> spam <module 'http://localhost:15000/spam.py' from 'http://localhost:15000/spam.py'> >>>
Как вы можете видеть, это работает для простых модулей. Однако функция не подключена к обычной инструкции import, а расширение кода для поддержки более продвинутых конструкций, таких как пакеты, потребует дополнительной работы.
На следующем шаге мы продолжим изучение этого вопроса.