Python only has what you call "dynamic heterogeneous" structures because Python is a dynamic language. The ramifications of that are by no means limited to lists and tuples. It seems like you basically don't like dynamic languages.
Some of your comments make no sense even given the above, however. For example, Python lists and tuples certainly support iteration.
Erlang (one of my favorite languages) is dynamically typed, yet did not (until the recent addition of maps) have dynamic heterogeneous structures. You cannot iterate over or resize tuples, and the static analyzer discourages heterogeneous lists. Both are immutable (functional).
I apologize that I have hopped between use and implementation a bit in my above post. Yes, the implementation of Python lists and tuples support iteration, but it would be nonsensical to write code which iterated over a list or tuple containing heterogeneous data. (What could you possibly do identically to each element, if they had nothing in common?) Hence my assertion that implementing a dynamic heterogeneous structure is inefficient, if it nonsensical to truly use it as such.
At that point it is an argument of taste, but my belief is that those rare cases are invariably code smells and can be rewritten more clearly without the necessary run-time type introspection (or inference from some other information). Better to restrict them entirely IMHO.
I hope it is obvious that Python values are members of a single static-type, this massive sum-type that combines a type-tag with a value, summing together all of the possible types-in-the-python-sense. That is to say, from the perspective of static typing, dynamic languages are uni-typed.
In languages with explicit static types and explicit Tagged Unions, it's not unreasonable to iterate across a homogeneous sequence of tagged unions, all of the same type. For example, Option<Int>. This is just a particular instance of the general pattern of iterating across a heterogeneous sequence, introspecting on whether a given value is of type Int or of type NoneType. Unless you want to allege that Null is a member of Integer (in a sane and healthy type system).
The point of this is that I'm skeptical that these are "invariably code smells". For example, our Option<Int> values might come from a SQL field that is a nullable integer. It seems entirely fair to iterate across them.
Maybe it is not clear from my other comments but we are in violent agreement :) Tagged unions (and even more general RTTI) have a common interface and therefore are homogeneous: a well-written iteration always accesses such elements first through the tagged-union (or RTTI) interface.
To me, code which acted on such elements without first consulting the common interface would be "smelly". e.g. code which assumes every other item is a string, or code which uses a list as an ad-hoc structure. There's almost always a better way to write such code.
(As an aside, I find the explicit use of RTTI a smell in and of itself, but one unrelated to containers. Code which today does two different things to some input, based on whether it's an integer or string, likely tomorrow will need to do three different things, two of which involve an integer. Tagged unions are almost always preferable.)
The nuance where I disagree with you was the point of my comment. I claim that there's no principled distinction between tagged unions and RTTI. (Except that of course usually implementations that refer to themselves with the phrase "tagged union" are a little more, uh, sophisticated, in exactly the kind of way that your comment about "likely tomorrow will need to do three different things.." alludes to eventually having a need for.)
Unless you can draw a principled distinction, then you can't on the one hand say "sequences of tagged unions are homogeneous" and on the other say "python sequences are potentially heterogeneous, and if you have a tuple of heterogeneous data and you iterate on it, that's invariably a code smell". Can you?
I mean, unless by "code smell" you mean "thing that's worth a double-look, because although it might be good code, it's easy for it to be evil code". In which case, we're in total agreement.
See my other comment; it does make sense to have a list of objects which share some interface. In the case you suggest, it is a very trivial and not-very-useful interface, but an interface nonetheless. You can do this with objects even in strictly-typed languages like OCaml or Mercury; code which iterates over the list is simply restricted to use only those methods which all elements have in common.
Of course in languages which don't support a universal "object" type (say OCaml or C) you cannot do this -- numbers and strings have no interface in common. Those languages take advantage of this by not storing type information in collections (e.g. OCaml's float arrays, or any array in C), thus resulting in more compact memory footprints. If they instead allowed heterogeneous collections, the memory footprint of such collections would double.
> There are Python functions that take any type like repr() or id()
Umm.. I haven't had to use any of them on a list(or for that matter on an object) for code.. (except when I'm curious and poking at python internals) May be I'm stuck in a web application role? Is it actually useful to say call repr() on a list of objects? May be call id to see if there's references to same object twice? I'm finding it hard to imagine cases where it would be needed to implement a feature, that wouldn't be better served by alternate methods.
One could argue that for those, the semantic differences between the types are dropped. In OO lingo, they look at the properties of the common base class.
I feel like your reply here, and perhaps a few other comments, are confusing a few senses of the word "dynamic" (which, to be fair, is i.m.o. a reasonable confusion).
Python is "a dynamic language" mostly in the sense that it is "dynamically typed". (I'm sure you're aware, I'm not trying to patronize, just setting the stage.) That's one sense.
Tuples in Python are still dynamic in this sense, of course, because every damn value in Python is, for good or for ill. But there are at least two other senses in play here.
Compare C structs with, say, JS Objects. Both let you mutate the values they point to, but JS Objects let you add new property-names at will (or remove them). So one sense of "dynamic" is "resizeable".
We could also have a data type, e.g. linked lists in various "purely functional" languages, that can be resized (pushed and popped), but existing cells cannot be mutated. AIUI Clojure is full of this sort of thing.
Point #1 is that nothing in colanderman's comments argues against Python's dynamic typing.
My second and broader thought is that I agree with colanderman that it's weird that Python conflates these latter two senses of dynamism (resizeability and cell-mutability), and even more weird that it does so for one data type (the sequence) and more or less ignores it elsewhere (though e.g. frozendict does exist).
There's nothing stopping you from writing Python types that separate these different senses of "dynamic". You can write a sequence type that doesn't let you change the number of cells, but does let you mutate existing cells. Or you can write one that lets you add new cells, but doesn't let you mutate cells once they are added. You can even write them as C extensions if you want the same speed and memory efficiency as the built-in types.
Some of your comments make no sense even given the above, however. For example, Python lists and tuples certainly support iteration.