Define Python function in Julia file with PythonCall?

Context: I have a Pluto notebook for teaching where I need to create a Python function. I want to use PythonCall for better portability, but I miss the py"..." syntax of PyCall which allows you to directly define Python functions inside Julia code. Is there a workaround to importing the function from a file? I looked at @py but it doesn’t seem to work for my case.

3 Likes

Do you require the function to be defined in Python, or could you use something like pyfunc as described in Reference - PythonCall - Create classes ?

Ideally I’d want the function to be defined in Python to demonstrate the Julia speed up. Dumping on Python is my favorite pastime

3 Likes
julia> using PythonCall

julia> fib(x) = pyexec("""
       def fib(n):
            a, b = 0, 1
            while a < n:
                print(a, end=' ')
                a, b = b, a+b
            print()
       fib(y)
       """, Main, (y=x,))
fib (generic function with 1 method)

julia> fib(10)
0 1 1 2 3 5 8

https://cjdoris.github.io/PythonCall.jl/stable/pythoncall-reference/#PythonCall.pyexec

6 Likes

Thanks a bunch!

1 Like

For future reference, we don’t need to redefine the Python function every time we call the Julia one, we can also access local variables within it:

julia> fib(n) = pyexec(
           @NamedTuple{a::Int},
           """
           a, b = 0, 1
           for k in range(n):
               a, b = b, a + b
           """,
           Main,
           (n=n,)
       )
fib (generic function with 1 method)

julia> fib(10)
(a = 55,)

Whenever you use pyexec you’re invoking the Python parser, which makes for an unfair comparison. It’s better to use it to create a Python function then call that:

@pyexec """
def fib(n):
   a, b = 0, 1
   while a < n:
       print(a, end=' ')
       a, b = b, a+b
   print()
""" => fib

fib(10)

This example uses @pyexec’s handy => notation for getting variables into and out of Python.

10 Likes

Thanks, that’s exactly what I was looking for

Thanks for the great extension to the PythonCall doc! What I can’t get to work yet is an import within the literal code, or some way to get an import (e.g. numpy) to work in or around your fibonacci. There must be a way?
Thanks for any tips!

1 Like

@cjdoris sorry for the necroposting but I find myself interested in this question again. Is there a way to achieve that?

1 Like

I found an alternative solution by using ** PythonCall.@pyexec**

function PYTHON_2_JULIA(P_Int, P_Float, P_String, P_Vector)

    PythonCall.@pyexec """
        def PYTHON_2_JULIA(P_Int, P_Float, P_String, P_Vector):
            P_Int1 = P_Int + 1
            P_Float1 = P_Float * 2.0
            P_String1 = P_String + P_String 
            P_Vector1 = P_Vector + P_Vector
            return P_Int1, P_Float1, P_String1, P_Vector1
        """=> PYTHON_2_JULIA

    P_Int1, P_Float1, P_String1, P_Vector1 = PythonCall.pyconvert(Any, PYTHON_2_JULIA(P_Int, P_Float, P_String, P_Vector))

return P_Int1, P_Float1, P_String1, P_Vector1
end

To run it:

P_Int=1; P_Float=2.0; P_String=3; P_Vector = [1.0, 2.0, 3.0, 4.0]
P_Int1, P_Float1, P_String1, P_Vector1 = .PYTHON_2_JULIA(P_Int, P_Float, P_String, P_Vector)

@show P_Int1, P_Float1, P_String1, P_Vector1

P_Vector1[1]

This gives as expected

(2, 4.0, 6, [2.0, 4.0, 6.0, 8.0])
2.0