# Functions in Python: def, return, Arguments, *args and **kwargs, lambda and Type Hints

URL: https://skillaria.top/en/guides/python/python-functions-def-return-lambda

A function in Python is a named block of code declared with the word def, called by its name with brackets, and returns a result through return; without return a function gives back None. Arguments are passed by position or by name, *args collects extra positional values into a tuple, **kwargs collects named ones into a dictionary, and lambda creates a short anonymous function from a single expression.

A function in Python is a named piece of code that can be called as many times as you like with different input data. It is declared with the word `def`, takes values through parameters and gives back a result through `return`. Functions are the main way in Python to turn a long "wall" of script into a set of clear, testable steps.

## How to declare a function: def in Python

A function declaration starts with `def`, followed by the name, parameters in brackets and a colon. The body is written with an indentation of four spaces — indentation in Python is not decoration but part of the syntax.

```
def greet(name):
    """Returns a greeting for the user."""
    return f"Hello, {name}!"

print(greet("Anna"))   # Hello, Anna!
```

A triple-quoted string right after the header is the docstring, the function's documentation. It is shown by `help(greet)` and by editor hints. By PEP 8 convention, function names are written in snake_case: `add_expense`, `get_rate`.

The line `def greet(name):` is called the function's signature: the name plus the list of parameters (and annotations, if any). From the signature a reader understands how to call the function without looking into its body.

## What return does in Python

`return` ends the function and hands a value back to whoever called it. Code after an executed `return` does not run. If there is no `return`, or it is written without a value, the function returns `None`.

```
def area(w, h):
    w * h          # the result is computed and thrown away

print(area(3, 4))  # None

def area(w, h):
    return w * h

print(area(3, 4))  # 12
```

A forgotten `return` is one of the most common beginner mistakes: the function "works", but gives back `None`, and the error surfaces a line later. You can also return several values — in fact this is one tuple, which is convenient to unpack:

```
def min_max(numbers):
    return min(numbers), max(numbers)

low, high = min_max([4, 1, 9])
print(low, high)   # 1 9
```

An early `return` helps avoid extra nesting: first you handle the special cases, then you write the main logic.

```
def discount(price, is_member):
    if price  limit    # reading a global — allowed

def raise_limit():
    global limit
    limit = 2000             # without global, a local variable would be created
```

In practice `global` is best avoided: a function that changes external state is hard to test. It is more reliable to accept the value as an argument and return the result.

## Nested functions and closures

A function can be declared inside another function. A nested function sees the variables of the outer one even after the outer one has finished — this is a closure. To change such a variable, the `nonlocal` declaration is used.

```
def make_counter():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

c = make_counter()
print(c(), c(), c())   # 1 2 3
```

Closures are the basis of decorators and function factories.

## A function is also an object

In Python a function is an ordinary object: it can be put into a variable, passed to another function, returned from a function, stored in a dictionary. It has attributes: `__name__`, `__doc__`, `__defaults__`, `__annotations__`.

```
def double(x):
    return x * 2

f = double
print(f(5), f.__name__)                # 10 double
print(list(map(double, [1, 2, 3])))   # [2, 4, 6]
```

## Lambda in Python: anonymous functions

A lambda function is a short function without a name, whose body consists of a single expression. The result of the expression is returned automatically; you must not write `return`.

```
square = lambda x: x ** 2
print(square(4))   # 16

expenses = [("Coffee", 250), ("Taxi", 600), ("Lunch", 450)]
expenses.sort(key=lambda e: e[1], reverse=True)
print(expenses)  # [('Taxi', 600), ('Lunch', 450), ('Coffee', 250)]

print(list(filter(lambda n: n % 2 == 0, range(10))))  # [0, 2, 4, 6, 8]
```

Lambda functions in Python are appropriate as a short argument for `sorted`, `max`, `map`, `filter`. PEP 8 does not recommend assigning a lambda to a variable, as `square` above does: if a function needs a name, write `def` — it will have a proper `__name__` in the traceback and room for a docstring.

|  | def | lambda |
| --- | --- | --- |

| Name | yes | no (`<lambda>`) |

| Body | any number of statements | one expression |

| return | explicit | implicit |

| Docstring, annotations | yes | no |

| Where it fits | everywhere | a short key or callback |

## Type annotations: hints, not checks

Annotations describe the expected types of parameters and the result. Python itself does not check them at run time: a function with `x: int` will happily accept a string. The checking is done by external tools — mypy, pyright, ty — and by the editor, which highlights errors before the code runs.

```
def add_expense(title: str, amount: float, category: str = "other") -> dict[str, object]:
    return {"title": title, "amount": amount, "category": category}

def area(w: float, h: float) -> float:
    w * h   # mypy: Missing return statement
```

You can run the check without installing anything with `uvx mypy .`. The example above shows the value of annotations: a forgotten `return` with `-> float` is caught by the analyser at once.

Since Python 3.12 (PEP 695), generic functions are written without `TypeVar`: the type parameter is given in square brackets, and a type alias is declared with the word `type`.

```
def first[T](xs: list[T]) -> T:
    return xs[0]

type Money = int | float
```

In Python 3.14 (PEP 649), annotations became lazy: they are evaluated on first access, so you can refer to a class declared further down without quotes, and `from __future__ import annotations` is no longer needed.

## "Not passed" or "passed as None": the sentinel in 3.15

A default of `None` does not let you distinguish "the argument was not passed" from "None was passed deliberately". For years this was solved with a home-made placeholder:

```
_MISSING = object()

def get_rate(currency, default=_MISSING):
    rates = {"USD": 90.5}
    if currency in rates:
        return rates[currency]
    if default is _MISSING:
        raise KeyError(currency)
    return default
```

Python 3.15 added a built-in function `sentinel` (PEP 661): `MISSING = sentinel("MISSING")` creates a unique marker with a readable repr, which stays itself when copied, is serialised through pickle, and can be used in an annotation such as `float | MISSING`. On older versions the `object()` technique continues to work.

## Common mistakes with functions

- **Forgotten return** — the function returns `None`.
- **Forgotten brackets** — `print(greet)` prints the function object, not the result of the call.
- **A mutable default value** — one list shared across all calls.
- **Wrong number of arguments** — `TypeError: greet() missing 1 required positional argument: 'name'`.
- **A name that clashes with a built-in** — a function called `list` or `sum` shadows the built-in.

## Summary

Functions in Python are declared with `def`, return a result with `return`, and accept arguments by position or by name. `*args` and `**kwargs` give flexibility, `/` and `*` give strictness of calls, lambda gives brevity where a one-off function is needed, and type annotations together with mypy catch errors before the code runs. In our Python course this topic is practised hands-on: the expense tracker is split into annotated functions and checked with mypy.

## FAQ

**How is a parameter different from an argument?**

A parameter is a name in the function declaration; an argument is the specific value passed at the call. In def greet(name) the name is a parameter, and in greet("Anna") the string "Anna" is an argument.

**Can I call a function before it is declared?**

At module level — no: the def line must run before the call, otherwise you get a NameError. But inside another function you can call a function declared below, as long as the call happens after its declaration.

**How many values can a function return?**

Formally one, but that value can be a tuple: return a, b returns a tuple of two items, which is unpacked as x, y = f().

**Are type annotations mandatory?**

No, Python works without them. But in code that other people read, annotations make maintenance much easier and let mypy or pyright find errors before the code runs.

**Can I write an if inside a lambda?**

Only a conditional expression: lambda x: "even" if x % 2 == 0 else "odd". Statements, loops and assignments are not allowed inside a lambda.


---
Source: Skillaria.Top (https://skillaria.top)
