Executes a Python-style for
loop in R, iterating over a Python iterable or
iterator. This provides a convenient syntax for looping with destructuring
(tuple unpacking) similar to Python's for
statement.
Usage
py_for(loop_spec, body, env = parent.frame())
Arguments
- loop_spec
A two-sided formula of the form
vars ~ iterable
, wherevars
specifies one or more loop variables (e.g.x
orc(i, j)
), anditerable
is a Python iterable or iterator.- body
An R expression to evaluate on each iteration.
- env
The environment in which to run the loop and evaluate the body. Defaults to the calling environment.
Details
If
iterable
implements only__iter__
but not__next__
, it is automatically converted into an iterator.Loop variables support tuple unpacking via
py_tuple_unpack()
.The loop tracks whether the user calls
break
ornext
inside the loop:
Performance warnings
Looping over Python objects in R can be inefficient. In each iteration,
reticulate
must pass handles between R and Python, often performing
implicit or explicit object conversions and copies.
If the body
of your loop is lightweight and you need to iterate over a
large Python object, consider defining a Python function via
reticulate::py_run_string()
or py_builtins$exec()
and calling it directly.
You can also use r.var
(where var
is any R variable name) to access or
assign R objects directly from Python, which may help avoid unnecessary data transfer.
Native Python tools are significantly faster in such cases!
For example, instead of doing:
it is better to do:
function_def <- "
def cumsum_square(n):
return sum([x ** 2 for x in range(1, n + 1)])
"
my_func <- reticulate::py_run_string(function_def, local = TRUE, convert = FALSE)
y <- my_func$cumsum_square(100000L)
Note that we need to set local = TRUE
, so that the returned dictionary
is not within the main module. The main module created by reticulate
automatically converts Python objects to R objects, unless we disable this
behavior for the entire module. Doing so, however, could interfere
with reticulate
's internals. Defining the function in a private dictionary
with convert = FALSE
allows us to keep objects as native Python types,
which is important when working with large integers that could otherwise
overflow when converted back to R.
Examples
if (FALSE) { # \dontrun{
# Loop over a Python list
py_for(x ~ reticulate::r_to_py(list(1, 2, 3)), {
print(x)
})
# Loop with tuple unpacking
pairs <- reticulate::tuple(list(list(1, "a"), list(2, "b")), convert = TRUE)
py_for(c(i, j) ~ pairs, {
cat("i =", i, " j =", j, "\n")
})
# Loop over a NumPy array
np <- reticulate::import("numpy", convert = FALSE)
arr <- np$array(c(10, 20, 30))
py_for(val ~ arr, {
print(val)
})
# Basic loop over a Python list with loop control
py_for(x ~ reticulate::r_to_py(list(1, 2, 3)), {
if (reticulate::py_to_r(x) == 2) next # skip printing 2
if (reticulate::py_to_r(x) == 3) break # exit before printing 3
print(x)
})
# Nested loop
py_for(x ~ reticulate::r_to_py(list(list(1, 2, 3), list(4, 5, 6))), {
py_for(y ~ x, {
print(y)
})
print("inner list finished")
})
} # }