# How to wrap a Python function in a Julia package using PythonCall.jl

**URL:** <https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101>\
**Category:** General Usage\
**Tags:** pythoncall\
**Created:** [February 23, 2023, 7:59pm UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101 "2023-02-23T19:59:29Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [February 23, 2023, 7:59pm UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/1 "2023-02-23T19:59:29Z")

</div>

I am developing a package that wants to wrap and re-export a Python function. The relevant code is below, and it works well within the same file it was called in, but it breaks when trying to test it in the package I am developing. Is there a way to use PythonCall to execute a Python function on a Julia array under the hood? Is there something I am doing that is noticeably wrong?

This works within the same file:

```julia
## /src/scipy.jl
using Pkg
Pkg.activate("..")
using CondaPkg; CondaPkg.add("scipy")

scipy = pyimport("scipy")
ndimage = scipy.ndimage

function transform(array, tfm::Scipy)
	return pyconvert(Array{Float32}, ndimage.distance_transform_edt(array))
end

x = [
	1 1 0 0
	0 1 1 0
	0 1 0 1
	0 1 0 0
]
test = transform(x, Scipy())

```

This is broken:

```julia
## /test/scipy.jl
x = [
	1 1 0 0
	0 1 1 0
	0 1 0 1
	0 1 0 0
]
test = transform(x, Scipy())

```

---

<div class="post-metadata">

**Author:** ![Jake](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jake/32/46007_2.png) [@Jake](https://discourse.julialang.org/u/Jake)\
**Post date:** [February 23, 2023, 8:10pm UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/2 "2023-02-23T20:10:40Z")

</div>

I am not a Python user nor have I ever done this. But I have used odsIO.jl very successfully, so there may be clues in that package.

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [February 23, 2023, 8:17pm UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/3 "2023-02-23T20:17:19Z")

</div>

Test segfaults with this error

```julia
signal (11): Segmentation fault: 11
in expression starting at /Users/daleblack/Library/CloudStorage/GoogleDrive-djblack@uci.edu/My Drive/dev/julia/DistanceTransforms/test/scipy2.jl:4
PyObject_GetAttr at /private/var/folders/t3/_k26tgtj7cv96l4vy3pxk5nw0000gn/T/jl_xyJmot/.CondaPkg/env/lib/libpython3.11.dylib (unknown line)
PyObject_GetAttr at /Users/daleblack/.julia/packages/PythonCall/ZzOaq/src/cpython/pointers.jl:299 [inlined]
pygetattr at /Users/daleblack/.julia/packages/PythonCall/ZzOaq/src/abstract/object.jl:60
getproperty at /Users/daleblack/.julia/packages/PythonCall/ZzOaq/src/Py.jl:272
unknown function (ip: 0x2a1d1c09b)
ijl_apply_generic at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
transform at /Users/daleblack/Library/CloudStorage/GoogleDrive-djblack@uci.edu/My Drive/dev/julia/DistanceTransforms/src/scipy.jl:39
unknown function (ip: 0x2a1d100e7)
ijl_apply_generic at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
do_call at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
eval_body at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
eval_body at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
eval_body at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_interpret_toplevel_thunk at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_toplevel_eval_flex at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_toplevel_eval_flex at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_toplevel_eval_flex at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
ijl_toplevel_eval_in at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
eval at ./boot.jl:368 [inlined]
include_string at ./loading.jl:1428
ijl_apply_generic at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
_include at ./loading.jl:1488
include at ./client.jl:476
unknown function (ip: 0x100174057)
ijl_apply_generic at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
do_call at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
eval_body at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_interpret_toplevel_thunk at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_toplevel_eval_flex at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_toplevel_eval_flex at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
ijl_toplevel_eval_in at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
eval at ./boot.jl:368 [inlined]
include_string at ./loading.jl:1428
ijl_apply_generic at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
_include at ./loading.jl:1488
include at ./client.jl:476
unknown function (ip: 0x100174057)
ijl_apply_generic at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
do_call at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
eval_body at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_interpret_toplevel_thunk at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_toplevel_eval_flex at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_toplevel_eval_flex at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
ijl_toplevel_eval_in at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jlplt_ijl_toplevel_eval_in_13487 at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/sys.dylib (unknown line)
eval at ./boot.jl:368 [inlined]
exec_options at ./client.jl:276
_start at ./client.jl:522
jfptr__start_59061 at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/sys.dylib (unknown line)
ijl_apply_generic at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
true_main at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_repl_entrypoint at /Users/daleblack/.julia/juliaup/julia-1.8.5+0.aarch64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
Allocations: 72936179 (Pool: 72902612; Big: 33567); GC: 40
ERROR: Package DistanceTransforms errored during testing (exit code: 139)

```

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [February 23, 2023, 8:18pm UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/4 "2023-02-23T20:18:06Z")

</div>

Thanks, that might have some useful tips but I don’t see anything specific as of right now. Nice simple package though and uses PyCall, which is similar I think!

---

<div class="post-metadata">

**Author:** ![GunnarFarneback](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gunnarfarneback/32/1827_2.png) [@GunnarFarneback](https://discourse.julialang.org/u/GunnarFarneback)\
**Post date:** [February 23, 2023, 9:03pm UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/5 "2023-02-23T21:03:06Z")

</div>

With PyCall the key thing is to do the `pyimport` in the ` __init__ ()` function of the module. Precompilation saves memory pointers to Python objects otherwise, which won’t be valid in future sessions. I expect PythonCall behaves the same in this respect.

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [February 23, 2023, 9:34pm UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/6 "2023-02-23T21:34:15Z")

</div>

Okay, that makes some sense. I don’t see any documentation though for PythonCall. So with PyCall, if you have “PackageX” you would would create an ` __init__ ()` function inside of that main Module e.g. (`PackageX.jl`) or could if be in any `src` file?

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [February 23, 2023, 9:37pm UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/7 "2023-02-23T21:37:21Z")

</div>

This looks like it might be the answer [PyMNE.jl/PyMNE.jl at main · beacon-biosignals/PyMNE.jl · GitHub](https://github.com/beacon-biosignals/PyMNE.jl/blob/main/src/PyMNE.jl)

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [February 23, 2023, 9:47pm UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/8 "2023-02-23T21:47:07Z")

</div>

Adding this code inside the main module still results in a segfault

```julia
module DistanceTransforms
using PythonCall

const scipy = PythonCall.pynew()

function __init__ ()
    PythonCall.pycopy!(scipy, pyimport("scipy"))
end

export scipy

```

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [February 23, 2023, 10:27pm UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/10 "2023-02-23T22:27:55Z")

</div>

Can you post a full MWE please?

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [February 24, 2023, 9:40pm UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/11 "2023-02-24T21:40:45Z")

</div>

Yes, I just put together a simple example package ([GitHub - Dale-Black/DT.jl](https://github.com/Dale-Black/DT.jl)). This segfaults whenever trying to precompile and when trying to run the test. Thanks btw for the amazing package @cjdoris

---

<div class="post-metadata">

**Author:** ![GunnarFarneback](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gunnarfarneback/32/1827_2.png) [@GunnarFarneback](https://discourse.julialang.org/u/GunnarFarneback)\
**Post date:** [February 25, 2023, 8:41am UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/12 "2023-02-25T08:41:43Z")

</div>

```julia
ndimage = scipy.ndimage

function transform(array, tfm::Scipy)
    return pyconvert(Array{Float32}, ndimage.distance_transform_edt(array))
end

```

Be careful with all globals related to Python objects, as they get saved by the precompilation with stale pointers. Does it work better if you eliminate `ndimage` and call `scipy.ndimage.distance_transform_edt`?

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [February 25, 2023, 5:14pm UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/13 "2023-02-25T17:14:47Z")

</div>

Oh that looks like it fixed the problem! One more question, any recommended way to automatically install python packages like scipy inside of the Julia package if not already installed? I added this but I am guessing it’s not recommended

```julia
module DT

using CondaPkg
CondaPkg.add("scipy")

using PythonCall

const scipy = PythonCall.pynew()

function __init__ ()
    PythonCall.pycopy!(scipy, pyimport("scipy"))
end

export scipy

include("scipy.jl")

end

```

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [February 25, 2023, 7:15pm UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/14 "2023-02-25T19:15:09Z")

</div>

You just ship the CondaPkg.toml file that is created with your package. Any dependencies in there are automatically installed.

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [May 19, 2023, 2:06am UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/15 "2023-05-19T02:06:15Z")

</div>

Hey, just double checking. So I don’t need the `CondaPkg` dependency for this, right?

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [May 19, 2023, 2:08am UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/16 "2023-05-19T02:08:39Z")

</div>

As long as I have the CondaPkg.toml file, then PythonCall will handle the rest?

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [May 19, 2023, 7:10pm UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/17 "2023-05-19T19:10:00Z")

</div>

Yes, any package which depends on CondaPkg either directly or indirectly will have it’s CondaPkg.toml dependencies automatically installed.

Since PythonCall depends on CondaPkg, it’s enough to depend only on PythonCall.

---

<div class="post-metadata">

**Author:** ![sylvaticus](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sylvaticus/32/203883_2.png) [@sylvaticus](https://discourse.julialang.org/u/sylvaticus)\
**Post date:** [June 23, 2024, 12:25pm UTC](https://discourse.julialang.org/t/how-to-wrap-a-python-function-in-a-julia-package-using-pythoncall-jl/95101/18 "2024-06-23T12:25:37Z")

</div>

Thank you 🙂

(OdsIO developer looking for PythonCall documentation and ended up on this thread 🙂 🙂 🙂 )
