Basicamente, você está escolhendo um conjunto de erros off-by-one para outro. Um conjunto provavelmente fará com que seus loops sejam encerrados mais cedo, o outro provavelmente causará uma exceção (ou estouro de buffer em outros idiomas). Depois de escrever um monte de código, você verá que a escolha de comportamento range()faz sentido com muito mais frequência
@unutbu Esse artigo da Djikstra é freqüentemente citado neste tópico, mas não fornece nada de valor aqui, as pessoas o usam apenas como um apelo à autoridade. A única pseudo-razão relevante que ele dá para a pergunta do OP é que ele acha que incluir o limite superior se torna "antinatural" e "feio" no caso específico em que a sequência está vazia - essa é uma posição totalmente subjetiva e é fácil argumentar contra, por isso não traz muito para a mesa. O "experimento" com o Mesa não tem muito valor sem conhecer suas restrições ou métodos de avaliação.
sundar - Restabelece Monica
6
@andreasdr Mas, mesmo que o argumento cosmético seja válido, a abordagem do Python não apresenta um novo problema de legibilidade? No inglês de uso comum, o termo "intervalo" implica que algo varia de algo a algo - como um intervalo. Que len (lista (intervalo (1,2))) retorne 1 e len (lista (intervalo (2))) retorne 2 é algo que você realmente precisa aprender a digerir.
armin 24/07
Respostas:
245
Porque é mais comum chamar range(0, 10)what return [0,1,2,3,4,5,6,7,8,9]que contém 10 elementos iguais len(range(0, 10)). Lembre-se de que os programadores preferem a indexação baseada em 0.
Além disso, considere o seguinte snippet de código comum:
for i in range(len(li)):pass
Você poderia ver que, se range()fosse exatamente len(li)isso, seria problemático? O programador precisa subtrair explicitamente 1. Isso também segue a tendência comum de programadores preferindo for(int i = 0; i < 10; i++)mais for(int i = 0; i <= 9; i++).
Se você estiver chamando o intervalo com um início de 1 com frequência, convém definir sua própria função:
Se esse fosse o raciocínio, não seriam os parâmetros range(start, count)?
Mark Ransom
3
@shogun O valor inicial é 0, ou seja, range(10)é equivalente a range(0, 10).
moinudin
4
Você range1não trabalhará com intervalos com um tamanho de etapa diferente de 1.
dimo414
6
Você explica que o intervalo (x) deve começar com 0 e x será o "comprimento do intervalo". ESTÁ BEM. Mas você não explicou por que o intervalo (x, y) deve começar com x e terminar com y-1. Se o programador deseja um loop for com variação de 1 a 3, ele precisa adicionar explicitamente 1. Isso é realmente uma questão de conveniência?
Armin
7
for i in range(len(li)):é um antipadrão. Deve-se usar enumerate.
hans
27
Embora existam algumas explicações algorítmicas úteis aqui, acho que pode ajudar a adicionar alguns argumentos simples da "vida real" sobre o porquê de funcionar dessa maneira, que achei úteis ao apresentar o assunto para jovens recém-chegados:
Com algo como 'range (1,10)', pode surgir confusão ao pensar que um par de parâmetros representa o "começo e o fim".
Na verdade, é iniciar e "parar".
Agora, se fosse o valor "final", sim, você pode esperar que esse número seja incluído como entrada final na sequência. Mas não é o "fim".
Outros chamam erroneamente esse parâmetro de "contagem" porque, se você usar apenas 'range (n)', é claro que itera 'n' vezes. Essa lógica quebra quando você adiciona o parâmetro start.
Portanto, o ponto principal é lembrar o nome: " stop ". Isso significa que é o ponto no qual, quando alcançada, a iteração será interrompida imediatamente. Não depois desse ponto.
Portanto, enquanto "start" realmente representa o primeiro valor a ser incluído, ao atingir o valor "stop", ele "quebra", em vez de continuar processando "esse também" antes de parar.
Uma analogia que usei para explicar isso às crianças é que, ironicamente, é melhor se comportar do que crianças! Não para depois que deveria - para imediatamente sem terminar o que estava fazendo. (Eles entendem isso;))
Outra analogia - quando você dirige um carro, você não passa por um sinal de stop / yield / 'give way' e acaba com ele sentado em algum lugar próximo ou atrás do seu carro. Tecnicamente, você ainda não o alcançou quando para. Não está incluído nas "coisas que você passou na sua jornada".
Espero que isso ajude a explicar o Pythonitos / Pythonitas!
Você está tentando colocar batom em um porco. A distinção entre "parar" e "fim" é absurda. Se eu passo de 1 a 7, não passei por 7. É simplesmente uma falha do Python ter convenções diferentes para as posições de início e parada. Em outros idiomas, incluindo os humanos, "de X a Y" significa "de X a Y". No Python, "X: Y" significa "X: Y-1". Se você tem uma reunião das 9 às 11, você diz às pessoas que são das 9 às 12 ou das 8 às 11?
bzip2
24
Intervalos exclusivos têm alguns benefícios:
Por um lado, cada item range(0,n)é um índice válido para listas de comprimento n.
Também range(0,n)tem um comprimento de n, e não n+1um intervalo inclusivo.
Funciona bem em combinação com indexação baseada em zero e len(). Por exemplo, se você tiver 10 itens em uma lista x, eles serão numerados de 0 a 9. range(len(x))dá 0-9.
Obviamente, as pessoas dirão que é mais um Pythonic a fazer for item in xou for index, item in enumerate(x)melhor for i in range(len(x)).
O fatiamento também funciona da seguinte maneira: foo[1:4]são os itens 1-3 de foo(lembrando que o item 1 é realmente o segundo item devido à indexação com base em zero). Por consistência, os dois devem funcionar da mesma maneira.
Penso nisso como: "o primeiro número desejado, seguido pelo primeiro número que você não deseja". Se você quer 1-10, o primeiro número que você não quer é 11, então érange(1, 11) .
Se ficar complicado em um aplicativo específico, é fácil escrever uma pequena função auxiliar que adiciona 1 ao índice e chamadas finais range().
Concorde em fatiar. w = 'abc'; w[:] == w[0:len(w)]; w[:-1] == w[0:len(w)-1];
Kevpie
def full_range(start,stop): return range(start,stop+1) ## helper function
Nobar
talvez o exemplo enumerar deve ler for index, item in enumerate(x)a confusão evitar
seans
@seans Obrigado, corrigido.
Kindall
12
Também é útil para dividir intervalos; range(a,b)pode ser dividido em range(a, x)e range(x, b), enquanto que com o intervalo inclusivo você escreveria um x-1ou outro x+1. Embora raramente seja necessário dividir intervalos, você tende a dividir listas com bastante frequência, e esse é um dos motivos pelos quais o corte de uma lista l[a:b]inclui o a-ésimo elemento, mas não o a-ésimo. Então, rangeter a mesma propriedade a torna bastante consistente.
Gah! Eu não uso Ruby, mas posso imaginar que é difícil distinguir 1..10vs 1...10ao ler código!
moinudin
6
@marcog - quando você sabe que as duas formas existem os olhos sintonizar com a diferença :)
Skilldrick
11
O operador de gama do Ruby é perfeitamente intuitivo. Quanto maior o formato, menor a sequência. tosse
Russell Borogove
4
@ Russell, talvez 1 ............ 20 deva dar o mesmo intervalo que 1..10. Agora, isso seria um pouco de açúcar sintático pelo qual vale a pena mudar. ;)
kevpie
4
@Russell O ponto extra de aperta o último item fora da faixa :)
Skilldrick
5
Basicamente, em range(n)iterações python nvezes, que é de natureza exclusiva e é por isso que não fornece o último valor quando está sendo impresso, podemos criar uma função que fornece valor inclusivo, significa que também imprimirá o último valor mencionado no intervalo.
def main():for i in inclusive_range(25):print(i, sep=" ")def inclusive_range(*args):
numargs = len(args)if numargs ==0:raiseTypeError("you need to write at least a value")elif numargs ==1:
stop = args[0]
start =0
step =1elif numargs ==2:(start, stop)= args
step =1elif numargs ==3:(start, stop, step)= args
else:raiseTypeError("Inclusive range was expected at most 3 arguments,got {}".format(numargs))
i = start
while i <= stop:yield i
i += step
if __name__ =="__main__":
main()
É apenas mais conveniente argumentar em muitos casos.
Basicamente, poderíamos pensar em um intervalo como um intervalo entre starte end. Se start <= end, a duração do intervalo entre eles é end - start. Se lenfoi realmente definido como o comprimento, você teria:
len(range(start, end))== start - end
No entanto, contamos os números inteiros incluídos no intervalo em vez de medir a duração do intervalo. Para manter a propriedade acima verdadeira, devemos incluir um dos pontos de extremidade e excluir o outro.
Adicionar o stepparâmetro é como introduzir uma unidade de comprimento. Nesse caso, você esperaria
len(range(start, end, step))==(start - end)/ step
para comprimento. Para obter a contagem, basta usar a divisão inteira.
Essas defesas da inconsistência do Python são hilárias. Se eu quisesse o intervalo entre dois números, por que eu usaria a subtração para obter a diferença em vez do intervalo? É inconsistente usar diferentes convenções de indexação para posições inicial e final. Por que você precisaria escrever "5:22" para obter as posições de 5 a 21?
bzip2
Não é do Python, é bastante comum. Em C, Java, Ruby, o nome dele
Arseny 17/01
Eu quis dizer que é comum a indexação, não que os outros idiomas tenham necessariamente o mesmo tipo exato de objeto.
range()
faz sentido com muito mais frequênciaRespostas:
Porque é mais comum chamar
range(0, 10)
what return[0,1,2,3,4,5,6,7,8,9]
que contém 10 elementos iguaislen(range(0, 10))
. Lembre-se de que os programadores preferem a indexação baseada em 0.Além disso, considere o seguinte snippet de código comum:
Você poderia ver que, se
range()
fosse exatamentelen(li)
isso, seria problemático? O programador precisa subtrair explicitamente 1. Isso também segue a tendência comum de programadores preferindofor(int i = 0; i < 10; i++)
maisfor(int i = 0; i <= 9; i++)
.Se você estiver chamando o intervalo com um início de 1 com frequência, convém definir sua própria função:
fonte
range(start, count)
?range(10)
é equivalente arange(0, 10)
.range1
não trabalhará com intervalos com um tamanho de etapa diferente de1
.for i in range(len(li)):
é um antipadrão. Deve-se usarenumerate
.Embora existam algumas explicações algorítmicas úteis aqui, acho que pode ajudar a adicionar alguns argumentos simples da "vida real" sobre o porquê de funcionar dessa maneira, que achei úteis ao apresentar o assunto para jovens recém-chegados:
Com algo como 'range (1,10)', pode surgir confusão ao pensar que um par de parâmetros representa o "começo e o fim".
Na verdade, é iniciar e "parar".
Agora, se fosse o valor "final", sim, você pode esperar que esse número seja incluído como entrada final na sequência. Mas não é o "fim".
Outros chamam erroneamente esse parâmetro de "contagem" porque, se você usar apenas 'range (n)', é claro que itera 'n' vezes. Essa lógica quebra quando você adiciona o parâmetro start.
Portanto, o ponto principal é lembrar o nome: " stop ". Isso significa que é o ponto no qual, quando alcançada, a iteração será interrompida imediatamente. Não depois desse ponto.
Portanto, enquanto "start" realmente representa o primeiro valor a ser incluído, ao atingir o valor "stop", ele "quebra", em vez de continuar processando "esse também" antes de parar.
Uma analogia que usei para explicar isso às crianças é que, ironicamente, é melhor se comportar do que crianças! Não para depois que deveria - para imediatamente sem terminar o que estava fazendo. (Eles entendem isso;))
Outra analogia - quando você dirige um carro, você não passa por um sinal de stop / yield / 'give way' e acaba com ele sentado em algum lugar próximo ou atrás do seu carro. Tecnicamente, você ainda não o alcançou quando para. Não está incluído nas "coisas que você passou na sua jornada".
Espero que isso ajude a explicar o Pythonitos / Pythonitas!
fonte
Intervalos exclusivos têm alguns benefícios:
Por um lado, cada item
range(0,n)
é um índice válido para listas de comprimenton
.Também
range(0,n)
tem um comprimento den
, e nãon+1
um intervalo inclusivo.fonte
Funciona bem em combinação com indexação baseada em zero e
len()
. Por exemplo, se você tiver 10 itens em uma listax
, eles serão numerados de 0 a 9.range(len(x))
dá 0-9.Obviamente, as pessoas dirão que é mais um Pythonic a fazer
for item in x
oufor index, item in enumerate(x)
melhorfor i in range(len(x))
.O fatiamento também funciona da seguinte maneira:
foo[1:4]
são os itens 1-3 defoo
(lembrando que o item 1 é realmente o segundo item devido à indexação com base em zero). Por consistência, os dois devem funcionar da mesma maneira.Penso nisso como: "o primeiro número desejado, seguido pelo primeiro número que você não deseja". Se você quer 1-10, o primeiro número que você não quer é 11, então é
range(1, 11)
.Se ficar complicado em um aplicativo específico, é fácil escrever uma pequena função auxiliar que adiciona 1 ao índice e chamadas finais
range()
.fonte
w = 'abc'; w[:] == w[0:len(w)]; w[:-1] == w[0:len(w)-1];
def full_range(start,stop): return range(start,stop+1) ## helper function
for index, item in enumerate(x)
a confusão evitarTambém é útil para dividir intervalos;
range(a,b)
pode ser dividido emrange(a, x)
erange(x, b)
, enquanto que com o intervalo inclusivo você escreveria umx-1
ou outrox+1
. Embora raramente seja necessário dividir intervalos, você tende a dividir listas com bastante frequência, e esse é um dos motivos pelos quais o corte de uma listal[a:b]
inclui o a-ésimo elemento, mas não o a-ésimo. Então,range
ter a mesma propriedade a torna bastante consistente.fonte
O comprimento do intervalo é o valor superior menos o valor inferior.
É muito parecido com algo como:
em uma linguagem de estilo C.
Também como a linha de Ruby:
No entanto, Ruby reconhece que muitas vezes você deseja incluir o valor do terminal e oferece a sintaxe alternativa:
fonte
1..10
vs1...10
ao ler código!Basicamente, em
range(n)
iterações pythonn
vezes, que é de natureza exclusiva e é por isso que não fornece o último valor quando está sendo impresso, podemos criar uma função que fornece valor inclusivo, significa que também imprimirá o último valor mencionado no intervalo.fonte
Considere o código
A idéia é que você obtenha uma lista de comprimento
y-x
, que você pode (como você vê acima) iterar.Leia os documentos python para obter o range - eles consideram a iteração de loop for a principal maneira de usar.
fonte
É apenas mais conveniente argumentar em muitos casos.
Basicamente, poderíamos pensar em um intervalo como um intervalo entre
start
eend
. Sestart <= end
, a duração do intervalo entre eles éend - start
. Selen
foi realmente definido como o comprimento, você teria:No entanto, contamos os números inteiros incluídos no intervalo em vez de medir a duração do intervalo. Para manter a propriedade acima verdadeira, devemos incluir um dos pontos de extremidade e excluir o outro.
Adicionar o
step
parâmetro é como introduzir uma unidade de comprimento. Nesse caso, você esperariapara comprimento. Para obter a contagem, basta usar a divisão inteira.
fonte