diff --git a/Project.toml b/Project.toml index f2a099e0..3dea4f2e 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PyCall" uuid = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" authors = ["Steven G. Johnson ", "Yichao Yu ", "Takafumi Arakaki ", "Simon Kornblith ", "Páll Haraldsson ", "Jon Malmaud ", "Jake Bolewski ", "Keno Fischer ", "Joel Mason ", "Jameson Nash ", "The JuliaPy development team"] -version = "1.92.5" +version = "1.93.0" [deps] Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d" diff --git a/README.md b/README.md index 3554b4b4..d05ee329 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,8 @@ def sinpi(x): py"sinpi"(1) ``` +You can also execute a whole script `"foo.py"` via `@pyinclude("foo.py")` as if you had pasted it into a `py"""..."""` string. + When creating a Julia module, it is a useful pattern to define Python functions or classes in Julia's `__init__` and then use it in Julia function with `py"..."`. @@ -390,6 +392,11 @@ and also by providing more type information to the Julia compiler. `__init__`. Side-effect in Python occurred at top-level Julia scope cannot be used at run-time for precompiled modules. + You can also execute a Python script file `"foo.py"` by running `@pyinclude("foo.py")`, and it will be as if you had pasted the + script into a `py"..."` string. (`@pyinclude` does not support + interpolating Julia variables with `$var`, however — the script + must be pure Python.) + * `pybuiltin(s)`: Look up `s` (a string or symbol) among the global Python builtins. If `s` is a string it returns a `PyObject`, while if `s` is a symbol it returns the builtin converted to `PyAny`. (You can also use `py"s"` diff --git a/src/PyCall.jl b/src/PyCall.jl index 42713b9f..c09cc204 100644 --- a/src/PyCall.jl +++ b/src/PyCall.jl @@ -14,7 +14,7 @@ export pycall, pycall!, pyimport, pyimport_e, pybuiltin, PyObject, PyReverseDims pyraise, pytype_mapping, pygui, pygui_start, pygui_stop, pygui_stop_all, @pylab, set!, PyTextIO, @pysym, PyNULL, ispynull, @pydef, pyimport_conda, @py_str, @pywith, @pycall, pybytes, pyfunction, pyfunctionret, - pywrapfn, pysetarg!, pysetargs! + pywrapfn, pysetarg!, pysetargs!, @pyinclude import Base: size, ndims, similar, copy, getindex, setindex!, stride, convert, pointer, summary, convert, show, haskey, keys, values, diff --git a/src/pyeval.jl b/src/pyeval.jl index 4eca19d5..a312853f 100644 --- a/src/pyeval.jl +++ b/src/pyeval.jl @@ -232,3 +232,24 @@ macro py_str(code, options...) ret end end + +""" + @pyinclude(filename) + +Execute the Python script in the file `filename` as if +it were in a `py\"\"\" ... \"\"\"` block, e.g. so that +any globals defined in `filename` are available to +subsequent `py"..."` evaluations. + +(Unlike `py"..."`, however, `@pyinclude` does not +interpolate Julia variables into `\$var` expressions — +the `filename` script must be pure Python.) +""" +macro pyinclude(fname) + quote + m = pynamespace($__module__) + fname = $(esc(fname)) + pyeval_(read(fname, String), m, m, Py_file_input, fname) + nothing + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 2e826080..4594cc40 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -819,3 +819,13 @@ if lowercase(get(ENV, "JULIA_PKGEVAL", "false")) != "true" include("test_venv.jl") include("test_build.jl") end + +@testset "@pyinclude" begin + mktemp() do path, io + print(io, "foo1 = 1\nbar2 = 2\n") + close(io) + @pyinclude(path) + @test py"foo1" == 1 + @test py"bar2" == 2 + end +end