Вызов функций 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
.
Устранение неполадок
Ниже описывается решение некоторых распространенных проблем.
-
По умолчанию PyCall не включает текущий каталог в путь поиска Python. Если вы хотите сделать это (для загрузки модуля Python из текущего каталога), просто выполните
pushfirst!(pyimport("sys")."path", "")
.
Интерфейсы для работы с объектами 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...)
. Вызывает указанную функцию Pythonfunction
(обычно из модуля) с указанными аргументами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]))"
вызывает функцию Pythonsum
для массива 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"""..."""
определена глобальная переменная Pythong
, ее можно получить в 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)
выполняет ту же проверку для определенного объекта типа Pythont
.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).