> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lazycloud.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Functions

> Python that runs in a remote container and returns its result, one call at a time or fanned out over a batch.

A function fits batch jobs, background work, and GPU tasks that your code or a
person calls. HTTP callers suit an [endpoint](/concepts/endpoints), and
recurring runs a [schedule](/concepts/schedules).

## Define a function

Save this as `reports.py`:

```python theme={null}
from lazycloud import App
from pydantic import BaseModel

app = App("reports")


class SalesSummary(BaseModel):
    sales: int
    total_cents: int


@app.function(cpu=1, memory="256Mi", timeout_seconds=60, retries=0)
def summarize_sales(amounts_cents: list[int]) -> SalesSummary:
    print(f"Summarizing {len(amounts_cents)} sales", flush=True)
    return SalesSummary(sales=len(amounts_cents), total_cents=sum(amounts_cents))
```

## Call it

From the CLI, JSON arguments become Python values:

```bash theme={null}
uv run lazycloud run reports:summarize_sales '[1200, 3500, 800]'
```

From Python:

```python theme={null}
from reports import summarize_sales

print(summarize_sales.remote([1200, 3500, 800]))
```

Both run in a cloud container, stream its logs to your terminal, and return
three sales totaling 5500 cents. `async_remote()` is the async form, and a plain
`summarize_sales(...)` call runs locally.
[Where a call goes](/concepts/workflow#where-a-call-goes) covers previews and
deployments.

### Process a batch

`.map()` submits one call per input and yields results in input order. Each
tuple holds one call's arguments:

```python theme={null}
from reports import summarize_sales

for result in summarize_sales.map([([1200, 3500],), ([800, 900],)]):
    print(result)
```

A failed call yields `None`. When every item must succeed before you use the
batch, `spawn_map()` returns each outcome to check:

```python theme={null}
calls = summarize_sales.spawn_map([([1200, 3500],), ([800, 900],)])
outcomes = [call.result(wait=True) for call in calls]
failed = [outcome.error for outcome in outcomes if not outcome.ok]
if failed:
    raise RuntimeError(failed)
print([outcome.value for outcome in outcomes])
```

Without an autoscaler the calls run one at a time in one container. An
autoscaler lets the function start more containers and run them in parallel:

```python theme={null}
from lazycloud import Autoscaler


@app.function(
    cpu=1,
    memory="512Mi",
    max_pending_tasks=1000,
    autoscaler=Autoscaler(max_containers=20, tasks_per_container=1),
)
def count_words(text: str) -> int:
    return len(text.split())
```

A `.map()` over 1,000 texts now runs on up to 20 containers at once.
`max_pending_tasks` goes up because `.map()` submits every input at once. See
[Scaling](/concepts/scaling) and the
[Parquet example](/examples/parallel-parquet-s3).

### Submit work in the background

```python theme={null}
call = summarize_sales.spawn([1200, 3500, 800])
print(call.task_id)
value = call.get()
```

`.spawn()` returns a `FunctionCall` right away, and `.get()` waits for the
value. `uv run lazycloud task result <task-id>` collects it from anywhere else,
and a pending `FunctionCall` passed to another function arrives as its result.
[Tasks and logs](/concepts/tasks-and-logs) covers looking calls up by ID.

### Arguments and results

Calls from the Python SDK carry any serializable Python value, including NumPy
arrays, dataclasses, custom class instances, tuples, and bytes:

```python theme={null}
import numpy as np
from lazycloud import App, Image

app = App("matrices")
image = Image(python_version="3.12").add_python_packages(["numpy>=2,<3"])


@app.function(image=image)
def matrix() -> np.ndarray:
    return np.arange(6, dtype=np.int64).reshape(2, 3)
```

The caller needs the same packages and class definitions. Results are
unpickled, so they should come from code you trust.

HTTP calls, `lazycloud run --json`, and
[exported clients](/concepts/apps#call-deployed-workloads-from-other-projects)
need JSON, which Pydantic models and dataclasses convert to. Arguments and
results cap at 16 MiB each, so larger output goes in a
[volume](/concepts/volumes) or an [artifact](/concepts/artifacts).

## Function options

### Retries and timeouts

`timeout_seconds` caps one attempt, and `retries`, 3 by default, adds attempts
after a failure. One call can therefore run four times, so writes should be
safe to repeat, for example keyed on the input, or `retries=0`. A `RetryPolicy`
adds backoff:

```python theme={null}
import urllib.request

from lazycloud import RetryBackoff, RetryPolicy


@app.function(
    retry_policy=RetryPolicy(
        max_attempts=4,
        delay_seconds=2,
        backoff=RetryBackoff.Exponential,
        max_delay_seconds=30,
    ),
)
def fetch_text(url: str) -> str:
    with urllib.request.urlopen(url, timeout=30) as response:
        return response.read().decode("utf-8")
```

A [preempted](/concepts/resources#preemptible-capacity) call retries too, even
when its code never failed.

### Lifecycle hooks

`on_start` runs once before a container accepts calls, which suits loading a
model or opening a client:

```python theme={null}
from lazycloud import GpuType

MODEL = {}


def load_model() -> None:
    MODEL["classifier"] = load_classifier()  # your loading code


@app.function(gpu=GpuType.L4, on_start=load_model)
def classify(text: str) -> str:
    return MODEL["classifier"](text)
```

Per-call hooks are `on_running`, `on_success`, `on_error`, `on_retry`,
`on_failure`, and `on_finish`. `lazycloud.current_task_id()` returns the
current call's ID inside the function or a hook.

## Stop and clean up

`lazycloud task cancel` cancels a pending call and `task stop` a running one. A
deployed function stays callable until stopped:

```bash theme={null}
uv run lazycloud deployment stop summarize_sales
uv run lazycloud deployment delete summarize_sales
```

`stop` keeps the definition for `deployment start`.
[`--prune`](/concepts/apps#remove-workloads) removes a function deleted from
the code.

## Shared settings

[Images](/concepts/images), [secrets](/concepts/secrets),
[volumes](/concepts/volumes), [resources](/concepts/resources), and
[scaling](/concepts/scaling).
