Документация Engee

Вызов функций Python из языка Julia

Данный пакет обеспечивает возможность взаимодействия с Python, позволяя вызывать функции этого языка из кода на Julia. Вы можете импортировать любые модули Python в Julia, вызывать функции Python (с автоматическим преобразованием типов между Julia и Python), определять классы Python на основе методов Julia и передавать большие структуры данных между Julia и Python, не копируя их.

Указание версии Python

Если вы хотите использовать не версию Python по умолчанию, а какую-то другую, присвойте переменной среды PYTHON путь к исполняемому файлу python (или python3 и т. д.), а затем снова выполните Pkg.build("PyCall"). В Julia:

ENV["PYTHON"] = "... путь к исполняемому файлу python ..."
# ENV["PYTHON"] = raw"C:\Python310-x64\python.exe" # пример для Windows, "raw" позволяет не использовать escape-символы: "C:\\Python310-x64\\python.exe"

# ENV["PYTHON"] = "/usr/bin/python3.10"           # пример для *nix
Pkg.build("PyCall")

Имейте в виду, что команду Pkg.build("PyCall") также потребуется выполнить снова, если программа python существенно изменилась (например, вы перешли на новый дистрибутив Python либо перешли с Python 2 на Python 3).

Чтобы среда Julia использовала собственный дистрибутив Python (при настройке посредством Conda), просто присвойте переменной ENV["PYTHON"] пустую строку "" и повторно выполните Pkg.build("PyCall").

Текущая используемая версия Python хранится в глобальной переменной pyversion модуля PyCall. Имя библиотеки Python можно также найти в PyCall.libpython, а имя программы python — в PyCall.pyprogramname. Если используется дистрибутив Conda Python, PyCall.conda будет иметь значение true.

(Строго говоря, пакет PyCall не использует саму программу python: он подключается напрямую к библиотеке libpython. Однако для определения местонахождения libpython он запускает python во время выполнения Pkg.build.)

При последующих сборках PyCall (например, при обновлении пакета с помощью Pkg.update) по умолчанию будет использоваться то же имя исполняемого файла Python, если вы не зададите переменную среды PYTHON или не удалите файл Pkg.dir("PyCall","deps","PYTHON").

Примечание. Если вы работаете с виртуальными средами Python, учтите, что пакет PyCall по умолчанию использует виртуальную среду, в которой была выполнена его сборка, даже если вы потом сменили среду. Чтобы пакет PyCall использовал другую виртуальную среду, смените среду и выполните rm(Pkg.dir("PyCall","deps","PYTHON")); Pkg.build("PyCall"). В разделе Виртуальные среды Python ниже приводятся сведения о переключении виртуальной среды во время выполнения.

Примечание. Обычно необходимые библиотеки устанавливаются вместе с Python, но pyenv в MacOS необходимо установить с помощью команды env PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install 3.4.3. Дистрибутив Python Enthought Canopy в настоящее время не поддерживается. Как правило, мы рекомендуем использовать дистрибутив Python Anaconda в MacOS и Windows либо пакет Julia Conda, чтобы избежать лишних проблем.

Использование

Вот простой пример вызова функции Python math.sin:

using PyCall
math = pyimport("math")
math.sin(math.pi / 4) # returns ≈ 1/√2 = 0.70710678...

Преобразование производится автоматически для числовых, логических, строковых типов, потоков ввода-вывода, дат и периодов, типов-функций, а также для кортежей, массивов (списков) и словарей этих типов. (Функции Python можно преобразовывать и передавать в функции Julia, и наоборот!) Другие типы поддерживаются посредством универсального типа PyObject (см. ниже).

Для преобразования многомерных массивов между Python и Julia применяется интерфейс массивов NumPy. По умолчанию они передаются из Julia в Python без создания копии, а из Python в Julia — с ее созданием; преобразование массивов из Python в Julia без копирования можно реализовать с помощью типа PyArray (см. ниже).

Кроме того, можно передавать именованные аргументы. Например, интерфейс pyplot из matplotlib использует именованные аргументы для задания параметров графика. Эта функциональность доступна из Julia следующим образом:

plt = pyimport("matplotlib.pyplot")
x = range(0;stop=2*pi,length=1000); y = sin.(3*x + 4*cos.(2*x));
plt.plot(x, y, color="red", linewidth=2.0, linestyle="--")
plt.show()

Однако, чтобы обеспечить более эффективную интеграцию с бэкендами графики Julia и чтобы не прибегать к функции show, мы рекомендуем использовать matplotlib посредством модуля Julia PyPlot.

В подпрограммы Python, принимающие аргументы-функции, можно передавать любые функции Julia. Например, чтобы найти корень из cos(x) — x, можно вызвать решатель Newton из scipy.optimize таким образом:

so = pyimport("scipy.optimize")
so.newton(x -> cos(x) - x, 1)

Для имитации оператора with из Python имеется специальный макрос. Пример:

@pywith pybuiltin("open")("file.txt","w") as f begin
    f.write("hello")
end

Тип f можно указать как f::T (например, чтобы переопределить автоматическое преобразование, используйте f::PyObject). Аналогичным образом, если диспетчер контекста возвращает тип, который автоматически преобразуется в тип Julia, его потребуется переопределить посредством @pywith EXPR::PyObject ....

Если вы уже знакомы с Python, возможно, вам будет проще использовать py"..." и py"""...""", что эквивалентно функциям Python eval и exec соответственно:

py"""
import numpy as np

def sinpi(x):
    return np.sin(np.pi * x)
"""
py"sinpi"(1)

Весь скрипт "foo.py" также можно выполнить с помощью вызова @pyinclude("foo.py"), как если бы вы вставили его в строку py"""...""".

При создании модуля Julia полезно будет определить функции или классы Python в функции Julia __init__, а затем использовать их в функции Julia с помощью py"...".

module MyModule

using PyCall

function __init__()
    py"""
    import numpy as np

    def one(x):
        return np.sin(x) ** 2 + np.cos(x) ** 2
    """
end

two(x) = py"one"(x) + py"one"(x)

end

Обратите внимание, что код Python в py"..." в примере выше выполняется в пространстве имен Python, связанном с модулем MyModule. То есть функция Python one недоступна вне модуля MyModule.

Устранение неполадок

Ниже описывается решение некоторых распространенных проблем.

Интерфейсы для работы с объектами Python

PyCall предоставляет множество подпрограмм для работы с объектами Python в Julia посредством типа PyObject, описываемого ниже. Они позволяют более точно контролировать типы и данные, передаваемые между Julia и Python, а также получать доступ к дополнительным возможностям Python (в частности, связанным с низкоуровневыми интерфейсами, которые описываются ниже).

Типы

PyObject

Модуль PyCall также предоставляет новый тип PyObject (оболочку вокруг PyObject* в API C языка Python), который представляет собой ссылку на объект Python.

Имеются конструкторы PyObject(o) для ряда типов Julia. Кроме того, в PyCall есть функция convert(T, o::PyObject) для преобразования объектов PyObject обратно в типы Julia T. В настоящий момент поддерживаются такие типы, как числа (целые, вещественные и комплексные), логические значения, строки, потоки ввода-вывода, даты и периоды, функции, а также их кортежи и массивы (списки), но этот список планируется расширить. (Символы Julia преобразуются в строки Python.)

Если дан объект o::PyObject, o.attribute в Julia эквивалентно o.attribute в Python с автоматическим преобразованием типа. Чтобы получить атрибут как PyObject без преобразования типа, используйте вместо этого o."attribute". Функция keys(o::PyObject) возвращает массив доступных символов атрибутов.

Если дан объект o::PyObject, get(o, key) эквивалентно o[key] в Python с автоматическим преобразованием типа. Чтобы получить атрибут как PyObject без преобразования типа, используйте get(o, PyObject, key) или, в более общей форме, get(o, SomeType, key). Вы также можете указать значение по умолчанию, используемое, если ключ не найден, в виде get(o, key, default) или get(o, SomeType, key, default). Аналогичным образом, set!(o, key, val) эквивалентно o[key] = val в Python, в delete!(o, key) эквивалентно del o[key]. Для одного или нескольких целочисленных индексов o[i] в Julia эквивалентно o[i-1] в Python.

Вызвать o::PyObject можно в виде o(args...), как и в Python (при условии, что объект является вызываемым в Python). Явная форма pycall также может быть полезна в Julia, если вы хотите указать тип возвращаемого значения.

Функции pystr(o) и pyrepr(o) аналогичны соответственно str и repr в Python.

Массивы и PyArray

Из Julia в Python

Если у вас установлена библиотека NumPy (что верно по умолчанию при использовании Conda), то массив Julia a::Array, состоящий из совместимых с NumPy элементов, преобразуется посредством PyObject(a) в оболочку NumPy для тех же данных, то есть данные не копируются. Массивы Julia хранятся в памяти по столбцам, но так как NumPy поддерживает такие массивы, проблемы в этом нет.

Однако по умолчанию массивы NumPy, созданные в Python, хранятся в памяти по строкам, и некоторые пакеты Python будут выдавать ошибку при попытке передать в них массивы NumPy, хранящиеся по столбцам. Для решения этой проблемы можно воспользоваться функцией PyReverseDims(a), чтобы передать массив Julia как массив NumPy, хранящийся по строкам, с измерениями в обратном порядке. Например, если a — это массив Julia размерности 3x4x5, то PyReverseDims(a) передает его как хранящийся по строкам массив NumPy размерности 5x4x3 (не создавая копию исходных данных).

Объект Vector{UInt8} в Julia по умолчанию преобразуется в объект Python bytearray. Если вам вместо этого нужен объект bytes, можно воспользоваться функцией pybytes(a).

Из Python в Julia

Многомерные массивы NumPy (ndarray) поддерживаются и могут преобразовываться в собственный тип Julia Array с созданием копии данных.

Кроме того, модуль PyCall предоставляет новый тип PyArray (подкласс AbstractArray), который реализует не требующую копирования оболочку для массива NumPy (в настоящее время состоящего только из числовых типов или объектов). Просто используйте PyArray в качестве типа возвращаемого значения вызова pycall, возвращающего ndarray, или вызовите PyArray(o::PyObject) для объекта o типа ndarray. (Строго говоря, PyArray подходит для любого объекта Python, который использует интерфейс массивов NumPy для предоставления указателя на данные и сведений о форме.)

Напротив, при передаче массивов в Python типы Julia Array преобразуются в типы PyObject без создания копии посредством NumPy, например при передаче в качестве аргументов pycall.

PyVector

Модуль PyCall предоставляет новый тип PyVector (подкласс AbstractVector), который реализует не требующую копирования оболочку для произвольного списка или объекта последовательности Python. (В отличие от PyArray, тип PyVector не ограничивается массивами NumPy, хотя использовать для них PyArray, как правило, эффективнее.) Просто используйте PyVector в качестве типа возвращаемого значения вызова pycall, возвращающего список или объект последовательности (включая кортежи), или вызовите PyVector(o::PyObject) для объекта последовательности o.

v::PyVector поддерживает обычные ссылки и присваивание v[index], а также операции delete! и pop!. copy(v) преобразует v в обычный объект Vector в Julia.

PyDict

Как и в случае с PyVector, PyCall также предоставляет тип PyDict (подкласс Association), который реализует не требующую копирования оболочку для словаря Python (или любого объекта, реализующего протокол сопоставления). Просто используйте PyDict в качестве типа возвращаемого значения вызова pycall, возвращающего словарь, или вызовите PyDict(o::PyObject) для объекта словаря o. По умолчанию PyDict — это словарь Any => Any (или, точнее, PyAny => PyAny) с выводом типов во время выполнения, но если словарь Python содержит известные фиксированные типы, можно использовать PyDict{K,V}, где K и V — это соответственно типы ключа и значения.

В настоящее время при передаче словаря Julia в Python создается его копия.

PyTextIO

Потоки IO Julia преобразуются в объекты Python, реализующие интерфейс RawIOBase, поэтому они могут использоваться для двоичного ввода-вывода в Python. Однако иногда код Python (особенно осуществляющий десериализацию) ожидает поток, реализующий интерфейс TextIOBase, который отличается от RawIOBase в основном тем, что функции read и readall возвращают строки, а не байтовые массивы. Если необходимо передать поток IO как объект текстового ввода-вывода, просто вызовите PyTextIO(io::IO), чтобы преобразовать его.

(По всей видимости, простого способа определить автоматически, ожидает ли Python возврата строк или двоичных данных аргументом потока, не существует. Кроме того, в отличие от Python, Julia не открывает файлы отдельно в «текстовом» или «двоичном» режиме, поэтому определить способ преобразования исходя лишь из того, как был открыт файл, невозможно.)

PyAny

При преобразовании типов тип PyAny указывает PyCall, что тип Python необходимо определить во время выполнения и преобразовать в соответствующий собственный тип Julia. То есть результат и pycall(func, PyAny, ...), и convert(PyAny, o::PyObject) автоматически преобразуется в собственный тип Julia (если это возможно). Это удобно, но немного снижает производительность (из-за накладных расходов на проверку типов во время выполнения и того факта, что JIT-компилятор Julia больше не может вывести тип).

Вызов Python

В большинстве случаев PyCall автоматически выполняет преобразование в соответствующие типы Julia, проверяя объекты Python во время выполнения. Однако с помощью описанных ниже низкоуровневых функций можно точнее контролировать преобразование типов (например, использовать не требующий копирования тип PyArray для многомерного массива Python вместо копирования в Array). Использование pycall в случаях, когда тип возвращаемого значения Python известен заранее, также может повысить производительность как за счет устранения накладных расходов на вывод типа во время выполнения, так и благодаря предоставлению более полной информации о типе компилятору Julia.

  • pycall(function::PyObject, returntype::Type, args...). Вызывает указанную функцию Python function (обычно из модуля) с указанными аргументами args... (стандартных типов Julia, автоматически преобразуемых в соответствующие типы Python, если это возможно), преобразуя тип возвращаемого значения в returntype (используйте returntype PyObject, чтобы вернуть ссылку на непреобразованный объект Python, или PyAny, чтобы запросить автоматическое преобразование). Для удобства имеется макрос @pycall, который автоматически преобразовывает @pycall function(args...)::returntype в pycall(function,returntype,args...).

  • py"..." выполняет строку "..." как код Python (что равносильно функции eval в Python) и возвращает результат, преобразованный в PyAny. Есть также вариант py"..."o, который возвращает исходный объект PyObject (который затем при необходимости можно преобразовать вручную). Переменные и другие выражения Julia можно интерполировать в код Python (но не в строки Python, содержащиеся в коде Python) с помощью $. При этом интерполируется значение выражения (преобразованное в PyObject) — данные не передаются в виде строки, что отличает этот механизм от обычной интерполяции строк в Julia. Например, py"sum($([1,2,3]))" вызывает функцию Python sum для массива Julia [1,2,3] и возвращает значение 6. В свою очередь, если поставить ` перед интерполируемым выражением, то значение выражения будет вставлено как строка в код Python, что позволяет генерировать код Python с помощью выражений Julia. Например, если `x="1+1"` в Julia, то `py"$x"` возвращает строку `"1+1"`, а `py"x" возвращает значение 2. Если в py"""...""" передается многострочный текст, он может содержать произвольный код Python (а не просто одно выражение), но возвращаемым значением будет nothing. Это может быть полезно, например, для определения чистых функций Python и равносильно вызову функции exec в Python. (Если в многострочном тексте в вызове py"""...""" определена глобальная переменная Python g, ее можно получить в Julia, вычислив в дальнейшем py"g".)

    Если вызов py"..." используется внутри модуля Julia, действует пространство имен Python, связанное с этим модулем. Таким образом, вы можете определить функцию Python с помощью py"""....""" в своем модуле, не беспокоясь о конфликтах имен с другим кодом Python. Имейте в виду, что функции Python должны определяться в __init__. Следствием этого является то, что код Python в области Julia верхнего уровня не может использоваться во время выполнения для предварительно скомпилированных модулей.

    Файл скрипта Python "foo.py" также можно выполнить с помощью вызова @pyinclude("foo.py"), как если бы вы вставили его в строку py"...". (Однако @pyinclude не поддерживает интерполяцию переменных Julia с помощью $var — скрипт должен быть написан исключительно на Python.)

  • pybuiltin(s): ищет s (строку или символ) среди глобальных встроенных типов Python. Если s — это строка, возвращается PyObject, а если s — символ, возвращается встроенный объект, преобразованный в PyAny. (Для поиска встроенных типов или других глобальных объектов Python можно также использовать py"s".)

Иногда в Python может потребоваться передать именованный аргумент, являющийся зарезервированным словом в Julia. Например, вызов f(x, function=g) завершится сбоем, так как function является зарезервированным словом в Julia. В таких случаях можно воспользоваться низкоуровневым синтаксисом Julia f(x; :function=>g).

Вызов Julia из Python

Функции Julia преобразуются в вызываемые объекты Python, поэтому их можно легко вызывать из Python посредством аргументов функции обратного вызова. Модуль pyjulia позволяет напрямую вызывать функции Julia из Python, а также использует PyCall для преобразования типов.

Функция Julia f(args...) обычно преобразовывается в вызываемый объект Python p(args...): сначала аргументы Python преобразовываются в аргументы Julia посредством преобразования PyAny по умолчанию, затем вызывается f, и возвращаемое значение функции f в Julia преобразовывается обратно в объект Python с помощью преобразования PyObject(...) по умолчанию. Однако контролировать эти преобразования аргументов и возвращаемых значений можно на низком уровне, вызывая pyfunction(f, ...) или pyfunctionret(f, ...). Дополнительные сведения см. в документации по ?pyfunction и ?pyfunctionret.

Определение классов Python

@pydef создает класс Python, методы которого реализуются в Julia. Например:

P = pyimport("numpy.polynomial")
@pydef mutable struct Doubler <: P.Polynomial
    function __init__(self, x=10)
        self.x = x
    end
    my\_method(self, arg1::Number) = arg1 + 20
    x2.get(self) = self.x * 2
    function x2.set!(self, new\_val)
        self.x = new\_val / 2
    end
end
Doubler().x2

по сути эквивалентно следующему код Python:

import numpy.polynomial
class Doubler(numpy.polynomial.Polynomial):
    def __init__(self, x=10):
        self.x = x
    def my\_method(self, arg1): return arg1 + 20
    @property
    def x2(self): return self.x * 2
    @x2.setter
    def x2(self, new\_val):
        self.x = new\_val / 2
Doubler().x2

Аргументы и возвращаемые значения методов преобразовываются между Julia и Python автоматически. Поддерживаются все специальные методы Python (__len__, __add__ и т. д.).

@pydef поддерживает множественное наследование классов Python:

@pydef mutable struct SomeType <: (BaseClass1, BaseClass2)
    ...
end

Вот еще один пример с использованием Tkinter:

using PyCall
tk = pyimport("Tkinter")

@pydef mutable struct SampleApp <: tk.Tk
    __init__(self, args...; kwargs...) = begin
        tk.Tk.__init__(self, args...; kwargs...)
        self.label = tk.Label(text="Hello, world!")
        self.label.pack(padx=10, pady=10)
    end
end

app = SampleApp()
app.mainloop()

Поддерживаются также переменные классов:

using PyCall
@pydef mutable struct ObjectCounter
    obj\_count = 0 # Переменная класса
    function __init__(::PyObject)
        ObjectCounter.obj\_count += 1
    end
end

Циклы событий графического интерфейса

Для пакетов Python, имеющих графический интерфейс пользователя (GUI), в частности пакетов для построения графиков, таких как matplotlib (либо MayaVi или Chaco), цикл событий GUI (который обрабатывает такие события, как нажатия клавиш мыши) удобно начинать как асинхронную задачу в Julia, чтобы GUI мог реагировать на события, не блокируя приглашение к вводу Julia. В PyCall есть функции для реализации таких циклов событий для некоторых из наиболее распространенных кроссплатформенных наборов инструментов GUI: wxWidgets, GTK+ версии 2 (посредством PyGTK) или версии 3 (посредством PyGObject) и Qt (посредством модулей Python PyQt4 и PySide).

Настроить цикл событий GUI можно одним из следующих способов:

  • pygui_start(gui::Symbol=pygui()). Здесь gui может иметь значение :wx, :gtk, :gtk3, :tk или :qt в зависимости от того, для какого набора инструментов нужно запустить цикл событий. (При значении :qt будет использоваться PyQt4 или PySide с предпочтением в пользу первого варианта; если вам нужен конкретный набор из этих двух вариантов, укажите соответственно :qt_pyqt4 или :qt_pyside.) По умолчанию используется возвращаемое значение pygui(), то есть текущий GUI по умолчанию (см. ниже). Передав аргумент gui, можно также изменить GUI по умолчанию, как и при вызове pygui(gui) ниже. Циклы событий можно запускать сразу для нескольких наборов инструментов GUI, чтобы они выполнялись одновременно. Вызывать pygui_start несколько раз для одного и того же набора инструментов не имеет смысла (если только не нужно изменить текущий интерфейс pygui по умолчанию).

  • pygui(). Возвращает текущий набор инструментов GUI по умолчанию (Symbol). Если интерфейс GUI по умолчанию еще не задан, то это будет первый из наборов :tk, :qt, :wx, :gtk или :gtk3, для которого установлен соответствующий пакет Python. pygui(gui::Symbol) изменяет GUI по умолчанию на gui.

  • pygui_stop(gui::Symbol=pygui()): останавливает запущенный цикл событий для gui (по умолчанию это текущее возвращаемое значение pygui). Возвращает true, если цикл событий был запущен, или false в противном случае.

Для использования этих вспомогательных функций GUI с некоторыми библиотеками Python достаточно просто запустить цикл событий для соответствующего набора инструментов перед импортом библиотеки. Однако в других случаях необходимо явно сообщить библиотеке, какой набор инструментов GUI следует использовать и требуется ли интерактивный режим. Упростить эту задачу могут модули-оболочки для популярных библиотек Python, например модуль PyPlot для Julia.

Доступ к низкоуровневому API Python

Вызывать низкоуровневые функции из API Python на C можно с помощью ccall.

  • Чтобы получить указатель на функцию для передачи в ccall, используйте @pysym(func::Symbol), где func — это символ, имеющийся в API Python. Например, вызвать int Py_IsInitialized() можно так: ccall(@pysym(:Py_IsInitialized), Int32, ()).

  • В PyCall определен псевдоним типа PyPtr для типов аргументов PythonObject*, и аргументы PythonObject (см. выше) правильно преобразуются в этот тип. PythonObject(p::PyPtr) создает оболочку Julia вокруг возвращаемого значения PyPtr.

  • Для преобразования типов Julia в ссылки PyObject* и обратно используйте PyObject и описанные выше подпрограммы convert.

  • Если функция Python возвращает новую ссылку, сразу же преобразуйте возвращаемые значения PyPtr в объекты PythonObject, чтобы счетчики ссылок в Python уменьшались при удалении объекта сборщиком мусора в Julia, то есть PythonObject(ccall(func, PyPtr, ...)). Важно! Для подпрограмм Python, возвращающих заимствованные ссылки, следует вместо этого вызвать pyincref(PyObject(...)), чтобы получить новую ссылку.

  • Увеличивать и уменьшать счетчик ссылок вручную можно вызовами pyincref(o::PyObject) и pydecref(o::PyObject). Иногда это необходимо, если низкоуровневая функция крадет ссылку или возвращает заимствованную.

  • С помощью функции pyerr_check(msg::AbstractString) можно проверить, было ли выдано исключение в Python, и, если было, выдать исключение в Julia (включающее msg и объект исключения Python). Состояние исключения Python можно сбросить, вызвав pyerr_clear().

  • Функция pytype_query(o::PyObject) возвращает собственный тип Julia, в который можно преобразовать o, или PyObject, если преобразование невозможно.

  • С помощью функции pyisinstance(o::PyObject, t::Symbol) можно узнать, относится ли o к определенному типу Python (где t — идентификатор глобального объекта PyTypeObject в API Python на C). Например, pyisinstance(o, :PyDict_Type) проверяет, является ли o словарем Python. В свою очередь, pyisinstance(o::PyObject, t::PyObject) выполняет ту же проверку для определенного объекта типа Python t. pytypeof(o::PyObject) возвращает тип Python объекта o, как и функция type(o) в Python.

Использование PyCall из модулей Julia

PyCall можно использовать в любом коде на Julia, в том числе в модулях Julia. Однако использовать PyCall в предварительно скомпилированных модулях Julia следует с осторожностью. В первую очередь нужно помнить о том, что все объекты Python (любой объект PyObject) содержат указатели на области памяти, выделенные средой выполнения Python, и эти указатели нельзя сохранять в предварительно компилируемых константах. (При повторной загрузке предварительно скомпилированной библиотеки эти указатели будут содержать неверные адреса памяти.)

Решение достаточно простое.

  • Объекты Python, создаваемые в функциях, которые вызываются после загрузки модуля, всегда безопасны.

  • Если вы хотите сохранить объект Python в глобальной переменной, которая инициализируется автоматически при загрузке модуля, инициализируйте переменную в функции __init__ модуля. Глобальную константу со стабильным типом инициализируйте как PyNULL() на верхнем уровне, а затем используйте функцию copy! в функции __init__ вашего модуля, чтобы изменить ее значение на фактическое.

Например, предположим, что в вашем модуле используется модуль scipy.optimize и вы хотите загрузить его при загрузке вашего модуля, сохранив в глобальной константе scipy_opt. Это можно сделать так:

__precompile__() # этот модуль безопасен для предварительной компиляции
module MyModule
using PyCall

const scipy_opt = PyNULL()

function __init__()
    copy!(scipy_opt, pyimport_conda("scipy.optimize", "scipy"))
end

end

После этого к функциям scipy.optimize можно обращаться в виде scipy_opt.newton и т. д.

Здесь вместо pyimport мы использовали функцию pyimport_conda. Второй аргумент — это имя пакета Anaconda, который предоставляет этот модуль. Таким образом, если импортировать scipy.optimize не удастся из-за того, что пользователь не установил scipy, то либо (a) scipy установится автоматически и будет выполнена повторная попытка вызова pyimport, если для PyCall настроено использование дистрибутива Python Conda (или любого другого дистрибутива Python на основе Anaconda, для которого у пользователя есть права на установку), либо (b) произойдет ошибка с сообщением о том, что необходимо установить scipy, и указаниями по настройке PyCall с Conda для автоматической установки. Вы также можете вызвать pyimport_conda(module, package, channel) для указания дополнительного «канала» Anaconda с целью установки нестандартных пакетов Anaconda.

Виртуальные среды Python

Виртуальные среды Python, созданные с помощью venv и virtualenv, можно использовать из PyCall при условии, что исполняемый файл Python, применяемый в виртуальной среде, связан с той же библиотекой libpython, что и PyCall`. Обратите внимание, что виртуальные среды, создаваемые `conda, не поддерживаются.

Чтобы использовать PyCall с определенной виртуальной средой, перед импортом PyCall присвойте переменной среды PYCALL_JL_RUNTIME_PYTHON путь к исполняемому файлу Python. Пример:

$ source PATH/TO/bin/activate  # активируем виртуальную среду в системной оболочке

$ julia  # запускаем Julia
...

julia> ENV["PYCALL_JL_RUNTIME_PYTHON"] = Sys.which("python")
"PATH/TO/bin/python3"

julia> using PyCall

julia> pyimport("sys").executable
"PATH/TO/bin/python3"

Аналогичным образом, путь PYTHONHOME можно изменить с помощью переменной среды PYCALL_JL_RUNTIME_PYTHONHOME.

Автор

Данный пакет был создан Стивеном Г. Джонсоном (Steven G. Johnson).