In Python all values are objects with built-in type info. Variables are references to these values. So their type is 'dynamic', just equal to the type of what they happen to refer to (point to) at a particular moment.
Whenever memory is allocated for the contents of a variable, a value is available. Since it has a type, the amount of memory needed is known.
The references (variables) themselves always occupy the same amount of memory, no matter what they point to, since they just contain a conceptual address.
This indeed means that in
def f (x):
print (x)
x doesn't have a type, since it doesn't have a particular value yet.
The upside is that this is very flexible.
The downside is that the compiler has only limited means to discover errors.
For this reason Python was recently enriched with type hints.
Tools like mypy allow static typechecking, even though the interpreter doesn't need it.
But the programmer sometimes does, especially at module boundaries (API's) when she's working in a team.