Зачем в python range() если есть xrange()?
Сначала было слово и слово было Python... бла бла бла....
А потом добавили range, который создавал всю последовательность натуральных чисел в памяти и это было хорошо, так как можно было их итерировать.
А потом добавили xrange, который не добавлял весь набор в памяти, а вычислял следующий элемент, ничего не зная про остальные (предыдущие и последующие элементы), почти ничего не занимая в памяти. Так появились генераторы. И поняли, что генераторы это хорошо, отделили генераторы от итераторов и стало так.
А потом Девид Бизли на Пайконе 2008, высеченными на камне презентациями... ой я увлекся.
По большей части, xrange и range одинаковые с точки зрения функциональности. Они оба обеспечивают способ создания списка целых чисел. Разница лишь в том, что range возвращает список и xrange возвращает объект xrange.
Что это значит? Хороший вопрос! Это означает, что xrange на самом деле не генерируют статический список во время выполнения, как range. Он создает значения, как только они вам нужны специальным методом, называемым yielding. Эта методика используется с типом объекта, известного как генераторы. Если вы хотите прочитать более подробно о генераторах обратится к google.
Хорошо, теперь, что это значит? Еще один хороший вопрос. Это означает, что если у вас есть действительно гигантский диапазон для котого вы хотите создать список, скажем, один миллиард, нужно использовать xrange.
Если вы хотите пройти по списку несколько раз, то, вероятно, лучше использовать range. Так как xrange должен сгенерировать целое число каждый раз, когда вы обращаетесь к индексу, в то время как range статический список и значения уже "там"  и готовы к использованию.