For small integers and strings, that are expected to be frequently used, Python uses internal memory optimization. Since any variable in Python is a reference to memory object, Python puts such small values into the memory only once. Then, whenever the same value is assigned to any other variable, it makes that variable point to the object already kept in memory. This works for strings and integers as they are immutable and if the variable value changes, effectively it's the reference used by this variable that is changed, the object in memory with original value is not itself affected.
That's why variables foo1 and foo2 in first case hold reference to the same integer object in memory with value 4 and therefore the ids are the same.
First of all, floating point numbers are not 'small', and, second, the same 4.3 in memory depending on calculations might be kept as 4.3123456789 and 4.31239874654 (just example numbers to explain). So these two values are two different objects, but during calculations and displaying the meaningful part looks the same, i.e. 4.3 (in fact there's obviously many more possible values in memory for the same meaningful floating point number). So reusing the same floating point number object in memory is problematic and doesn't worth it after all.
That's why in the second case foo1 and foo2 reference different floating point object in memory and therefore have different ids.
See more details on how floating point numbers are kept in memory:
http://floating-point-gui.de/
http://docs.python.org/2/tutorial/floatingpoint.html
Also, there's a big article on floating point numbers at Oracle docs.
@josliber, I edited the answer before reposting as you advised.