In the case of Python, the things that professional caliber programmers can use to extend its functionality, and the things that the rest of us can use to be productive, are the same language and are not hidden from one another. This is not the case in some languages of the past, e.g., you could use objects in Visual Basic but not build them without another language or eventually a special version.
Dynamic typing and changing the type of a variable are orthogonal.
You can change a type with casting, coercion, transmutation, dynamic dispatch, downcasting, reflection, monkeypatching (i.e. simultaneosly using dynamic dispatch and downcasting and reflection). All of these methods are available in Rust, which is a statically typed language.
Some things that some people think are changing the type of a variable in Python is not what it seems, for example:
# Python
a = 1
a = ['h','i'] # `a: int` is unchanged but gets implicitly shadowed by `a: list[str]`
a[1] = "a" # We're combining shadowing with weird and inconsistent scoping
translates to the following in Rust: // Rust
#[allow(unused_mut, unused_variables)]
{
let mut a = 1;
let mut a = ["h", "i"]; // Explicit shadowing
a[1] = "a"; // We're simply calling a mutating operator
}
Furthermore: # Python
lst: Array[Any] = [1, "2", 3.0] # A heterogeneous list
for elem in lst:
print(elem);
translates to the following in Rust: // Rust
// Below `dyn` has nothing whatsoever to do with dynamic typing.
// ... it is just fluff having to do with the requirements of systems programming.
// ... (Technically it means we're using vtables to do dynamic dispatch.)
// ... The `Box` nonsense is more systems dev fluff having to do with memory allocation.
// ... Of course in Rust, App devs often don't need to worry about such fluff because
// ... lib devs should provide ergonomic API's, but I'm doing as close as
// ... possible to a direct translation of the Pyhon code.
#[allow(unused_mut)]
let mut lst: Vec<Box<dyn Display>> = vec![
Box::new(1),
Box::new("1"),
Box::new(1.0),
];
for elem in lst {
println!("{elem}");
}
The one major difference between the Python code and Rust code above is that `Display` in Rust is a Rust trait (i.e. a typeclass) and typeclasses don't exist in Python.
It's the community. Network effect looms large.
While typing is dynamic, I rarely see this being used, perhaps with the exception of identifying empty variables with None. Most identifiers are born and die the same type. It would be interesting to see how much effect dynamic typing has on the rate of programming errors.
Also, for me, Jupyter is huge.