100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Calling Python and C from Julia

How PyCall.jl/PythonCall.jl let Julia call Python libraries directly, and how Julia's built-in ccall lets you call C and Fortran functions with zero overhead.

Practical JuliaIntermediate10 min readJul 10, 2026
Analogies

Calling Python from Julia

PythonCall.jl (the modern successor to the older PyCall.jl) lets Julia code import and call Python packages directly, converting between Julia and Python data types automatically at the boundary — a Julia Vector{Float64} becomes a NumPy array, a Julia Dict becomes a Python dict, and vice versa. This is invaluable for reusing mature Python libraries that have no Julia equivalent yet — calling matplotlib for a specific plot type, using pandas for a format Julia's DataFrames.jl doesn't yet parse, or wrapping a pretrained scikit-learn or transformers model — without leaving Julia or rewriting the Python logic; the underlying mechanism embeds a real Python interpreter in the Julia process via libpython, so calls have real interpreter overhead but no serialization cost since data is shared in memory.

🏏

Cricket analogy: It's like an IPL franchise fielding a marquee overseas signing (Steve Smith) rather than rebuilding his exact skillset from scratch domestically — PythonCall lets Julia 'sign' mature Python libraries like scikit-learn instead of reimplementing them.

julia
using PythonCall

# Import a Python module and call it directly
np = pyimport("numpy")
plt = pyimport("matplotlib.pyplot")

x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)

plt.plot(x, y)
plt.savefig("sine_wave.png")

# Convert a Julia array to Python and back
julia_vec = [1.0, 2.0, 3.0]
py_arr = np.array(julia_vec)          # Julia -> Python
back_to_julia = pyconvert(Vector{Float64}, py_arr)  # Python -> Julia

PythonCall.jl manages its own isolated Conda/micromamba Python environment per Julia project by default (via CondaPkg.jl), so different Julia projects can depend on different, non-conflicting Python package versions without you managing virtualenvs manually.

Calling C and Fortran with ccall

Unlike calling Python, calling into a C shared library requires no wrapper package at all — ccall is a built-in Julia language construct that calls a C function directly with zero overhead, because Julia's own runtime is implemented largely in C and C already shares Julia's calling conventions at the machine level. The syntax ccall((:function_name, "libname"), ReturnType, (ArgType1, ArgType2), arg1, arg2) specifies the C function's symbol name, which shared library to load it from, its C return type, and a tuple of its C argument types, and Julia handles marshaling native Julia values (like Float64, Int32, or a Ptr{Float64} from an Array) into the exact bit representation the C ABI expects — no serialization, no interpreter, just a direct machine-code call.

🏏

Cricket analogy: It's like a direct, unbroken relay throw from deep cover straight into the keeper's gloves with no intermediate fielder — ccall makes a direct machine-code call to C with zero intermediate translation overhead, the way Ravindra Jadeja's flat, direct throws skip any relay stop.

Because ccall has no per-call overhead, it is the mechanism Julia's own standard library uses internally to wrap BLAS, LAPACK, and libc functions, and it's also how packages like Fortran90Wrappers.jl-style bindings call decades-old, battle-tested Fortran numerical routines directly — Fortran's bind(C) interoperability standard lets a Fortran subroutine expose a C-compatible ABI that ccall can then invoke exactly like any C function. The tradeoff is that ccall requires you to get argument types and memory layout exactly right — passing a Vector{Float64} where the C function expects a null-terminated Ptr{Cchar} string will not error at compile time, it will segfault or corrupt memory at runtime, since ccall bypasses Julia's normal type-safety checks by design.

🏏

Cricket analogy: It's like a fielder throwing directly at the stumps without going through the keeper — a perfectly aimed direct hit runs the batter out cleanly, but a slightly misjudged direct throw with no relay safety net can concede overthrows, just as a wrong ccall type has no safety net either.

ccall bypasses Julia's type-safety and garbage-collector guarantees. Passing incorrectly typed arguments, holding a Ptr past the lifetime of the Julia object it points to, or forgetting GC.@preserve around an array whose pointer you pass to a long-running C call are all common sources of memory corruption or segfaults that Julia's usual safety net does not catch.

Choosing Between the Two Approaches

In practice, reach for ccall when you need to call a compiled C or Fortran library directly for maximum performance and you're comfortable getting the low-level ABI details exactly right — this is how Julia itself talks to BLAS/LAPACK, and it's the right tool for wrapping a small, well-defined C API. Reach for PythonCall.jl when you want to reuse a large, complex Python library's actual logic (not just a thin C API) — machine learning frameworks, plotting libraries with rich stateful APIs, or anything where re-implementing the Python package in Julia (or writing a raw C binding to it) would be far more work than paying the modest interpreter-call overhead of talking to Python directly.

🏏

Cricket analogy: It's like choosing between fielding your own specialist death-over bowler (ccall — total control, needs precision) versus loaning a proven overseas all-rounder from another league for a season (PythonCall — reuse a complete, complex skillset without rebuilding it).

  • PythonCall.jl (successor to PyCall.jl) embeds a real Python interpreter and auto-converts data between Julia and Python types at the call boundary.
  • PythonCall manages an isolated Python environment per project via CondaPkg.jl, avoiding manual virtualenv management.
  • ccall is a built-in Julia language construct for calling C/Fortran functions directly with zero overhead, no wrapper package required.
  • ccall syntax specifies the C symbol name, library, return type, and argument types explicitly.
  • Fortran routines can be called via ccall using Fortran's bind(C) interoperability standard to expose a C-compatible ABI.
  • ccall bypasses Julia's type safety and GC guarantees; incorrect types or missing GC.@preserve can cause segfaults or memory corruption.
  • Choose ccall for direct, high-performance calls to small well-defined C/Fortran APIs; choose PythonCall.jl to reuse large, complex Python libraries without reimplementing them.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#JuliaStudyNotes#CallingPythonAndCFromJulia#Calling#Python#Julia#Fortran#StudyNotes#SkillVeris#ExamPrep