Both lambda and def create the same kind of function – they have the same kind of metadata and capabilities. Their technical difference is syntactical:
- A lambdais an expression producing a function.
- A defis a statement producing a function.
This is everything that dictates how they can be used. Other apparent differences simply come from the information lambda/def can capture.
>>> def def_func(): pass
>>> lambda_func = lambda: None
>>> type(def_func) == type(lambda_func)
True
Usage: Expression vs. Statement
A lambda is more flexible as expressions can be part of more language constructs.
#                v--------------v arguments must be expressions
sort(values, key=lambda x: abs(x))
In contrast, a def is more powerful as it can consist of more language constructs.
def encode(num, base):
    while num:   # statements must be inside statements
        num, bit = divmod(num, base)
        yield bit
These differences derive directly from one being an expression and the other being a statement. Python has no special rules to decide where a lambda/def may be used.
Where the wild <lambda>s grow
The primary reason to assume lambda and def correspond to different kinds of function is metadata: lambda is often referred to as an "anonymous function" and miraculously it always produces a function <lambda>. Other quirks include "lambda functions can't be pickled", and recently typing also does "not work" for lambda.
That is because compared to def syntax, the lambda syntax has no way of specifying name, type annotations and similar. As such, Python simply fills in sane defaults for either: the name becomes <lambda> and annotations are left empty.
>>> identity = lambda a: a
>>> identity.__qualname__
'<lambda>'
>>> identity.__annotations__
{}
Since <lambda> is not a valid identifier, everything using this metadata to find the function – most prominently pickle – fails.
However, that does not make the function an "anonymous function" type. The metadata can be patched up to insert what def would provide:
>>> identity.__qualname__ = identity.__name__ = 'identity'
>>> identity
<function __main__.identity(a)>
Of course at that one point one can just use def…