Olá Ricardo.
Muito legal que você notou isso.
Eu dei uma olhada e o array numpy guarda valores do tipo numpy_int64, que como pode ser visto na documentação,
representa no máximo o numero 9223372036854775807, então quando o for chega 63 multiplicação ele supera esse numero e deixa o valor 0. O mesmo problema não acontece no py_list porque ele utiliza o tipo int do Python que não tem limite (o limite depende do sistema que ele está rodando)
Para resolver isso, quando estamos criando o np_array, coloquei o parâmetro dtype para forçar esse limite, utilizei o tipo np.float64 que tem tamanho suficiente para guardar nossos valores.
np_array = np.arange(1000000,dtype=np.float64)
Entretanto testei o tempo do código tendo o estouro do tipo e sem o estouro e ambos tem tempo muito semelhantes, então o conceito passado pelo professor, que o np_array é mais performático, ainda é verdade.
Segue abaixo os testes que fiz:
1 - Sem estourar o máximo do tipo numpy_float64.
import numpy as np
np_array = np.arange(1000000,dtype=np.float64)
py_list = list(range(1000000))
%time for _ in range (100): np_array *= 2
%time for _ in range (100): py_list = [x*2 for x in py_list]
#verificando se as listas estão iguais apos a operação
print(np_array == py_list)
CPU times: user 39.6 ms, sys: 96 µs, total: 39.7 ms
Wall time: 40.8 ms
CPU times: user 7.25 s, sys: 211 ms, total: 7.46 s
Wall time: 7.54 s
[ True True True ... True True True]
2 - Estourando o máximo do tipo numpy_int64.
import numpy as np
np_array = np.arange(1000000)
py_list = list(range(1000000))
%time for _ in range (100): np_array *= 2
%time for _ in range (100): py_list = [x*2 for x in py_list]
#verificando se as listas estão iguais apos a operação
print(np_array == py_list)
CPU times: user 48.5 ms, sys: 980 µs, total: 49.5 ms
Wall time: 50 ms
CPU times: user 7.2 s, sys: 266 ms, total: 7.47 s
Wall time: 7.53 s
[ True False False ... False False False]
Mas muito obrigado por nos avisar, vou verificar o que pode ser feito.
E qualquer duvida não hesite em perguntar.
Bons estudos.