Skip to contents

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, where vars specifies one or more loop variables (e.g. x or c(i, j)), and iterable 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.

Value

Invisibly returns NULL. Called for side effects.

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 or next inside the loop:

    • break exits the loop early, skipping any remaining iterations.

    • next skips to the next iteration without stopping the loop entirely.

    • If neither is called, the loop proceeds normally.

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:

y <- py_builtins$int(0L)
py_for(x ~ reticulate::r_to_py(1:10000), y <- y + x^2)

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")
})


} # }