# RFC – ergonomic juliacall syntax for passing Python variables to Julia

**URL:** <https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266>\
**Category:** General Usage\
**Tags:** question, package, python, pythoncall\
**Created:** [December 30, 2024, 2:25am UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266 "2024-12-30T02:25:03Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [December 30, 2024, 2:25am UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/1 "2024-12-30T02:25:03Z")

</div>

I want to get feedback on an API for ergonomically passing Python variables to Julia code via juliacall. Here are a couple of ideas:

```python
from juliacall import Main as jl

# proposal
jl.teval("const MY_CONST = $(x)", x=my_python_const)

```

a “template” eval, or

```python
# proposal
with jl.let(x=my_python_const):
    jl.seval("const MY_CONST = x")

```

which is a juliacall “let.”

* * *

This is designed to address the following issue. Currently, passing Python objects to Julia via `seval` requires creating closure functions:

```python
# already works
jl.seval("x -> @eval const MY_CONST = $x")(my_python_const)

```

While Python’s PEP 750 proposes [Template Strings](https://peps.python.org/pep-0750/) which could offer one solution:

```python
# proposal
jl.seval(t"const MY_CONST = {my_python_const}")

```

We could implement something similar with a new `teval` method:

```python
# proposal
jl.teval("const MY_CONST = {my_python_const}")

```

However, this has two key issues:

1. The `{}` syntax conflicts with Julia type signatures (e.g., `Vector{Float64}`), which could cause subtle bugs. Even if t-strings are eventually merged to Python, we would still face this issue!
2. `teval` wouldn’t have access to local variables to reference `my_python_const`

Now, here are the two proposed approaches. The first is similar to Python’s `.format` method, but with `{} -> $()` to avoid conflicts.

```python
# proposal
jl.teval("const MY_CONST = $(x)", x=my_python_const)

```

The second approach uses a context manager for temporary variable binding:

```python
# proposal
with jl.let(x=my_python_const):
    jl.seval("const MY_CONST = x")

```

Basically the `seval` would have access to a stack of Python objects pushed via `juliacall.let`, and put them into the Julia context via a Julia-evaluated `let` statement.

Thoughts?

(I wanted to get broader feedback so am cross-posting [this PythonCall.jl/juliacall issue](https://github.com/JuliaPy/PythonCall.jl/issues/580) here)

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [December 30, 2024, 3:08am UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/2 "2024-12-30T03:08:28Z")

</div>

> [@MilesCranmer](#):
>
> ergonomically passing Python variables to Julia code

Wouldn’t it be easier and more natural to pass variables as function arguments, rather than defining globals or evaluating strings? Why is the latter something you find yourself needing frequently?

Going in the other direction, PyCall has long provided a way to interpolate Julia objects into Python-code strings, but it’s something I’ve virtually never used. Evaluating strings is a terrible, last-resort way to do cross-language calls.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [December 30, 2024, 3:24am UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/3 "2024-12-30T03:24:42Z")

</div>

@stevengj That’s actually what I’m currently doing - passing variables as function arguments via a closure, as shown in my first example:

```python
jl.seval("x -> @eval const MY_CONST = $x")(my_python_const)

```

The proposed APIs are just meant to provide a more ergonomic way to do this and other similar patterns, while maintaining proper variable scoping under the hood.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [December 30, 2024, 3:34am UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/4 "2024-12-30T03:34:18Z")

</div>

> [@stevengj](#):
>
> Why is the latter something you find yourself needing frequently?

This happens quite a bit in PySR - users frequently need to define Julia variables from Python to customize aspects of the symbolic regression search. It is very common for users to want to insert a single snippet of Julia code in their Python scripts. It comes up frequently enough that creating closures like this is a real problem, which is the entire reason I’m interested in a syntax like this.

Here are some recent examples of PySR’s users needing similar sorts of patterns for specific use-cases:

- Including a Python covariance matrix: [Is it possible to account for the covariance matrix in PySR? · MilesCranmer/PySR · Discussion #792 · GitHub](https://github.com/MilesCranmer/PySR/discussions/792#discussioncomment-11688242)
- Custom loss function with spearman correlation: [Symbolic Transformer · MilesCranmer/PySR · Discussion #789 · GitHub](https://github.com/MilesCranmer/PySR/discussions/789#discussioncomment-11670713)
- Constraining search constants to be real: [Constraining constants to be real for imaginary-value data · MilesCranmer/PySR · Discussion #768 · GitHub](https://github.com/MilesCranmer/PySR/discussions/768#discussioncomment-11496143)

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [December 30, 2024, 4:26am UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/5 "2024-12-30T04:26:33Z")

</div>

I think there’s a disconnect here, not sure. I think stevengj is talking about importing or executing files (like `pyexec(read(...))`) from another language and just using the interop wrappers afterward. There wouldn’t be any evaluation of inline strings of other languages at all.

> [@MilesCranmer](#):
>
> It is very common for users to want to insert a single snippet of Julia code in their Python scripts.

There is evidently a demand for it, and I agree there are benefits for inline code strings from other languages:

- Say we have code with a PJPJ structure (P for Python code, J for Julia code). Ideally we have a PP module and a JJ module to interop, but maybe we only had separate P and J scripts before and they weren’t designed with APIs. Sure, doing that work would be preferable, but pasting and interpolating is simpler and saves us time for other work.
- While it’s often exaggerated, lower technical skill of a team using scripting languages can affect how code is written and shared; one file or notebook is easier to read and attach in emails. Performance and best practices are not the priority or even needed for their job.
- Inline code strings can ironically stave off something worse in practice. A very common interop myth is that one can unconditionally optimize by moving source code into a “faster” language, so you’ll see forum questions about how to translate every single line in R code to RCall.jl code, completely unaware that Julia’s compiler cannot touch the underlying R code and the granular wrapping is adding significant overhead. If they’ll struggle to reimplement in Julia (many things lack neat equivalents) or write APIs for Julia interop, then interpolating into an `@R_str` is the best practice they can do.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [December 30, 2024, 4:49am UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/6 "2024-12-30T04:49:28Z")

</div>

@Benny Thanks. To clarify - my focus is on making it more ergonomic to pass Python variables into Julia when using `juliacall`. Currently, any time we want to use Python values in Julia code ( **from python** ), we need to create a separate closure:

```python
from juliacall import Main as jl
import numpy as np

my_python_const = np.array([1.0, 2.0])

jl.seval("x -> @eval const MY_CONST = $x")(my_python_const)
loss = jl.seval("""
threshold -> begin
    (x, y) -> min(abs2(x - y), threshold)
end
""")(1.0)

```

This pattern comes up a lot in PySR where Python users need to customize all sorts of Julia-side behavior. I’m exploring if we can make this variable-passing pattern more ergonomic and more intuitive to the Python user. I think the above would look cleaner as follows:

```python
with jl.let(x=my_python_const):
    jl.seval("const MY_CONST = x")

with jl.let(threshold=1.0):
    loss = jl.seval("(x, y) -> min(abs2(x - y), threshold)")

```

or

```python
jl.teval("const MY_CONST = $(x)", x=my_python_const)

loss = jl.teval("(x, y) -> min(abs2(x - y), $(threshold))", threshold=1.0)

```

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [December 30, 2024, 5:08am UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/7 "2024-12-30T05:08:40Z")

</div>

I’m not a user of PySR so don’t prioritize my opinion, but I’m not a fan of getting stuck in `with` blocks, and while I would glady use this:

> [@MilesCranmer](#):
>
> `loss = jl.teval("(x, y) -> min(abs2(x - y), $(threshold))", threshold=1.0)`

I would probably never define constants across a language barrier like this:

> [@MilesCranmer](#):
>
> `jl.teval("const MY_CONST = $(x)", x=my_python_const)`

Makes namespaces harder to read unless I have ALL the Julia code inlined to the Python file, in which case I wouldn’t be evaluating line by line like that.

Something to consider is that you’re doing this from the Python side, and `$`-interpolation is not a thing there. `$`-interpolation in foreign language code strings on the Julia side don’t treat `$` like native string or expression interpolation either, they need custom behavior for the interop. No idea how you’d do that from the Python side, probably best to implement it in Julia anyway.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [December 30, 2024, 5:14am UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/8 "2024-12-30T05:14:32Z")

</div>

Actually maybe it’s best if we just extend `seval` to pass keyword arguments by itself? So that you could just use

```python
jl.seval("const MY_CONST = x", x=my_python_const)

```

and have it work.

> **Potential implementation**
>
> and it would get parsed into the Julia code
> 
> ```julia
> (; x) -> @eval begin
> const MY_CONST = $x
> end
> 
> ```
> 
> and then called with `x=my_python_const` as a keyword argument.
> 
> The current handling for `seval` is here: [PythonCall.jl/src/JlWrap/module.jl at a61c0223a60b803016e51a0dde39371222abf773 · JuliaPy/PythonCall.jl · GitHub](https://github.com/JuliaPy/PythonCall.jl/blob/a61c0223a60b803016e51a0dde39371222abf773/src/JlWrap/module.jl#L12-L14)
> 
> ```julia
> function pyjlmodule_seval(self::Module, expr::Py)
> Py(Base.eval(self, Meta.parseall(strip(pyconvert(String, expr)))))
> end
> 
> ```
> 
> Perhaps there’s a way to preprocess the `expr` and convert it into a closure if keyword arguments are passed, and then execute it.

> [@Benny](#):
>
> Something to consider is that you’re doing this from the Python side, and `$`-interpolation is not a thing there. `$`-interpolation in foreign language code strings on the Julia side don’t treat `$` like native string or expression interpolation either, they need custom behavior for the interop.

This is precisely why I thought it would be a better option. See my comments about `{}` and [t-strings/PEP 750](https://peps.python.org/pep-0750/) in the first post. Using `{}` is not possible until t-strings arrive, and even then, it would cause a lot of bugs by the conflicting usage in Julia type parameters.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [December 30, 2024, 5:30am UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/9 "2024-12-30T05:30:28Z")

</div>

> [@MilesCranmer](#):
>
> it would cause a lot of bugs by the conflicting usage in Julia type parameters.

Yeah that would be my issue with it too. f-strings already `{}`-interpolate, just eagerly with the available variables, and you have to double braces `f"vec::Vector{{Int}}"` to escape the interpolation, which is a Julia code pasting issue we’d rather avoid.

I took another look, and turns out Python already has `Template` strings, just `from string import Template` instead of the proposed t-string literal, and it _does_ use `$`-interpolation (`${}` instead of `$()` like ours though), also escaped by doubling. It must be niche because I’ve never even heard of it, and PEP 292 says it’s inspired by the older `%`-interpolation in `str`. I’d probably lean toward `%` for more familiarity, but not sure if Python users would like that either. But hey if you decide to use `$()` (maybe it’s easier to handle in a Julia implementation), you can say Python has a precedent, it’s not just a Julia practice forced onto Python users.

[Template strings — Python 3.13.1 documentation](https://docs.python.org/3/library/string.html#template-strings)

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [December 30, 2024, 5:39am UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/10 "2024-12-30T05:39:51Z")

</div>

Cool. That `string.Template` looks a bit different from a “t-string” though? The main advantage of a t-string (Python 3.14+ if PEP 750 passes) is you could write:

```python
jl.seval(t"x = {y}")

```

and it would pass the entire variable for `y` (i.e., without converting it to a string) along with the parsed string object to `seval` for further processing. Only if you call `print` on it would it convert `y` to a string. I guess it’s most similar to a `LazyString` in Julia.

From PEP 750:

> Templates provide developers with access to the string and its interpolated values before they are combined. This brings native flexible string processing to the Python language and enables safety checks, web templating, domain-specific languages, and more.

Then in the juliacall side we would convert it to a Julia variable before finally passing it to the `Base.eval`

But yeah, sadly we couldn’t use it because `{}` conflicts too much with other syntax in Julia.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [December 30, 2024, 6:04am UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/11 "2024-12-30T06:04:10Z")

</div>

> [@MilesCranmer](#):
>
> That `string.Template` looks a bit different from a “t-string” though?

Indeed, `string.Template` only stores the raw uninterpolated `str`. I was mostly shocked they’re reusing the terminology, as niche as it was.

> [@MilesCranmer](#):
>
> ```julia
> t"x = {y}"
> 
> ```
> 
> and it would pass the entire variable for `y`

Not the variable `y` exactly, its name and its assigned object at the time. It’s not like closures capturing variables that react to reassignments. However, it does mean that you can extract those then-assigned objects OR evaluate the name for reassigned objects. In Julia, `$`-interpolation in `Expr` or custom string literals behave like the former.

> [@MilesCranmer](#):
>
> I guess it’s most similar to a `LazyString` in Julia.

No, `LazyString`s are mutable, it’s how they can lazily interpolate for printing. t-string `Template`s are immutable, and they are inputs for processing to separate `str`. Being more similar to `Expr` serves your purpose of evaluating Julia code. Again, just the really annoying need to escape `{{}}` that justifies custom parsing of raw `str`.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [December 30, 2024, 8:09am UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/12 "2024-12-30T08:09:08Z")

</div>

What do you think about just extending `seval` to handle keyword arguments directly? Could be the simplest solution for Python users.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [December 30, 2024, 1:06pm UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/13 "2024-12-30T13:06:48Z")

</div>

You mean like the `threshold` keyword argument here?

> [@MilesCranmer](#):
>
> `loss = jl.teval("(x, y) -> min(abs2(x - y), $(threshold))", threshold=1.0)`

Sure I’d use it, it just delays interpolation to a function call, and Python users would be familiar with that through the older printf-style `%`-formatting built into `str` or the `str.format` function using `{}` (both escape by doubling). But I’d (and my hunch is most users) prefer directly interpolating from variables in the scope, and that can’t be delayed at all. The proposed t-strings immediately store objects even if they don’t instantiate the full `str` like f-string literals. However, you can’t just make a non-standard string literal in Python, let alone for custom interpolation, which is why interpolation of arguments is common.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [December 30, 2024, 1:37pm UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/14 "2024-12-30T13:37:48Z")

</div>

> [@MilesCranmer](#):
>
> Currently, any time we want to use Python values in Julia code ( **from python** ), we need to create a separate closure:

My point is that this assumes that the Julia code is of the form of a “script”, whose parameters are defined in terms of global variables.

Ordinary best practice should be that your Julia code defines functions already, in which case you don’t need to create a separate closure with an `@eval` to define Julia globals from Python values. All nontrivial Julia code should normally be inside functions.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [December 30, 2024, 1:38pm UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/15 "2024-12-30T13:38:10Z")

</div>

FWIW, Pluto/HypertextLiterals have a very convenient way to share Julia values with JavaScript: interpolation like

```julia
@htl """<script>
x = $myval
</script>"""

```

doesn’t do string transformation, but efficiently packs and sends Julia `myval` to JavaScript (I think through msgpack). It’s seamless while it works (for a lot of cases!), with one downside that debugging can be challenging when anything goes wrong.

Maybe something similar could be done for Python → Julia direction, worth looking for inspiration.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [December 30, 2024, 1:46pm UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/16 "2024-12-30T13:46:20Z")

</div>

> [@aplavin](#):
>
> Maybe something similar could be done for Python → Julia direction

The difficulty is rooted in Python lacking macros that can transform code within an arbitrary scope ([proposed in PEP 638](https://peps.python.org/pep-0638/)). The similar-looking decorators are actually higher order functions that take arguments. There’s no customizable nonstandard string literals, it’s all builtin.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [December 30, 2024, 2:25pm UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/17 "2024-12-30T14:25:16Z")

</div>

> [@stevengj](#):
>
> Ordinary best practice should be that your Julia code defines functions already, in which case you don’t need to create a separate closure with an `@eval` to define Julia globals from Python values. All nontrivial Julia code should normally be inside functions.

The closure pattern with `@eval` has a helpful purpose here - it lets users automatically use Python objects directly in Julia functions, like in this [covariance matrix example](https://github.com/MilesCranmer/PySR/discussions/792#discussioncomment-11688242) where they just reference a numpy array. While I agree that pure Julia code should generally avoid this pattern, Python users seem to prefer to keep as much of their code in Python as possible – considering the popularity of Cython, Triton compiler, CuPy, etc. Keeping the barriers as low as possible for the Python community is a priority of PySR.

That said, we actually find that as users get more advanced, many graduate to using Julia and SymbolicRegression.jl directly where they can follow these better patterns! PySR’s more permissive style is really about providing an accessible entry point for Python users. I view this as a nice aspect of juliacall in general. And syntaxes like discussed in this thread could strengthen it even more.

> [@aplavin](#):
>
> FWIW, Pluto/HypertextLiterals have a very convenient way to share Julia values with JavaScript: interpolation like
> 
> ```julia-auto
> @htl """<script>
> x = $myval
> </script>"""
> 
> ```
> 
> doesn’t do string transformation, but efficiently packs and sends Julia `myval` to JavaScript (I think through msgpack). It’s seamless while it works (for a lot of cases!), with one downside that debugging can be challenging when anything goes wrong.

@aplavin Thanks for the pointer! Will take a look. The msgpack approach is particularly intriguing.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [December 30, 2024, 2:38pm UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/18 "2024-12-30T14:38:58Z")

</div>

> [@Benny](#):
>
> There’s no customizable nonstandard string literals, it’s all builtin.

It’s a real pity they don’t have this. It looks like it was proposed but got rejected as part of PEP 750:

 ![Screenshot 2024-12-30 at 14.32.24](https://global.discourse-cdn.com/julialang/original/3X/9/a/9a2bc467e1297ae8680d334d4eb539fde3503afc.png)

^I feel like their reasons for rejection don’t match, at all, my experience working with `_str` macros in Julia which I have really liked. Maybe the proposed design was presented with a particular flaw attached to it, and that aspect took down the whole idea with it. Bummer!

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [December 30, 2024, 3:12pm UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/19 "2024-12-30T15:12:47Z")

</div>

> [@MilesCranmer](#):
>
> it lets users automatically use Python objects directly in Julia functions, like in this [covariance matrix example](https://github.com/MilesCranmer/PySR/discussions/792#discussioncomment-11688242) where they just reference a numpy array

The Julia code is defining a function in that example, so why not just make `INV_COV_MATRIX` a parameter of your Julia function? Using globals to pass data to functions is an anti-pattern.

(Even if you then want to pass the function to another function that doesn’t expect the extra parameter, that’s what closures are for, e.g. you pass `lambda tree, dataset, options: custom_loss(tree, dataset, options, inv_cov)`.)

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [December 30, 2024, 3:38pm UTC](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266/20 "2024-12-30T15:38:51Z")

</div>

@stevengj The loss function interface in PySR is fixed - it must take (tree, dataset, options) as arguments since it’s called internally by the search algorithm. A scoped closure approach would look like:

```python
jl.seval("""
inv_covar -> begin
    function my_loss(tree, dataset, options)
        ...
    end
end""")(inv_cov)

```

This is effectively equivalent to the current pattern - we’ve just moved the closure around. The core question here isn’t about code organization or global-vs-scoped, but rather about making Python-\>Julia variable passing more ergonomic.

[Next page](https://discourse.julialang.org/t/rfc-ergonomic-juliacall-syntax-for-passing-python-variables-to-julia/124266.md?page=2)
