Open
Description
I'm not an expert and don't know the internals, but is there a reason PyCall can't do whatever PythonCall / juliacall does that lets the user use any Python executable, including ones with a statically linked libpython? Is there anything preventing what they're doing to be used here? A probably related question posted here: JuliaPy/PyCall.jl#988
Activity
oschulz commentedon Jul 17, 2022
That would be so awesome!
mkitti commentedon Nov 8, 2022
Here is cjdoris's response to marius311 on the topic:
https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/16
Indeed, he's right:
https://docs.python.org/3/library/ctypes.html
oschulz commentedon Nov 8, 2022
@mkitti so PyCall/pyjulia could do that as well?
mkitti commentedon Nov 8, 2022
I think so. We technically just need the pointer.
oschulz commentedon Nov 8, 2022
Oh that would be awesome! I guess Packages like PySr (@MilesCranmer), diffeqpy (@ChrisRackauckas) and so on would profit a lot from that as well.
mkitti commentedon Nov 23, 2022
I would like to review the situation here.
Part of the issue is that pyjulia is only half of the equation here. The other half is PyCall.jl.
In JuliaPy/PyCall.jl#612, they were trying to load the
python
executable as libpython due to PIE (Position Independent Executables).In the linked comment above, @cjdoris demonstrates that we do not need to load python executable or libpython since we could just reuse
ctypes.pythonapi._handle
as is done in juliacall / PythonCall. In juliacall, the pointer is passed through an environment variable.How is
ctypes.pythonapi._handle
loaded when Python is statically linked to libpython?Looking into
ctypes
we see thatpythonapi
is set toPyDLL(None)
. Thename
argument and the_name
field of PyDLL, a subclass of CDLL is set toNone
._name
is subsequently passed to_dlopen
which on POSIX systems is just libdl C routinedlopen
.If we look at the man page for
dlopen(3)
we see this call todlopen
will return a handle to the executable.Can we obtain the pythonapi pointer handle with
dlopen
in Julia?This suggests that we can use
dlopen
from Julia to obtain the same pointer. While there are a few layers of indirection involved, passing an empty string to Julia'sLibdl.dlopen
appears to work.We see above that the pointer from
ctypes.pythonapi._handle
is exactly the same pointer we obtain by invokingLibdl.dlopen("")
in Julia.Can we obtain symbols from this pointer?
Concluding statements
We can obtain
ctypes.pythonapi._handle
by callingdlopen("")
in Julia when started from Python. Forjuliacall
an environment variable may not have be used to transmit the pointer. Forpyjulia
and PyCall.jl this simplifies the method to obtainpythonapi
pointer.cjdoris commentedon Nov 23, 2022
That's cool!
I just took a quick look from JuliaCall and it's true
dlopen("")
returns the same handle on Linux, but it throws an error on Windows:Plus the behaviour of
dlopen("")
is undocumented, so personally I'm steering clear of it.mkitti commentedon Nov 23, 2022
While I agree that
dlopen("")
is undocumented at the Julia API level, it does correspond to the documented behavior at thr C API level.The use of
ctypes.pythonapi._handle
is also equally undocumented. The underlying mechanism basically depends on the same behavior.cjdoris commentedon Nov 23, 2022
Actually
ctypes.pythonapi
is documented to be aPyDLL
andPyDLL._handle
is documented to be the system handle - in this case the underscore is not indicating an internal attribute, but is to avoid name clashes with symbols in the DLL.mkitti commentedon Nov 23, 2022
You're right, I concede the point.
https://docs.python.org/3/library/ctypes.html#ctypes.PyDLL._handle
Also
dlopen("")
does not work on macOS and really should bedlopen(C_NULL)
which doesn't work. See JuliaLang/julia#22318. One would have to doThat does work.
xref: JuliaLang/julia#22318
mkitti commentedon Nov 23, 2022
On macOS
ctypes.pythonapi._handle
is0xfffffffffffffffe
.This is actually the value of
RTLD_DEFAULT
: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dlsym.3.htmlcjdoris commentedon Nov 23, 2022
🤯
I've never actually tried JuliaCall on Mac. I wonder if it works. I should really set up tests and CI.
Edit: It works fine! And indeed the handle is that special value.
That very last sentence ("this can be a costly search") may explain why loading in ~100 symbols takes so long in PythonCall (~1sec), one reason why PyCall is much faster to load.
mkitti commentedon Nov 23, 2022
On macOS, you can just
dlopen
the executable. At the moment the timing does not look terrible.PyCall.jl does a lot of symbol loading during precompilation. That is also going to make it difficult for using this pointer though and is also why it doesn't work with a statically linked python executable unless
compiled_modules = false
(e.g. no precompilation).My thought is that this could benefit from a lazy symbol loading scheme such as the one I put into GR.jl:
https://github.com/jheinen/GR.jl/blob/db3e5f53738be892b23317d673179a32b0e50910/src/funcptrs.jl#L74-L86
cjdoris commentedon Nov 24, 2022
Yeah thanks, I've got something similar in a branch somewhere....