Warum enthält der Bereich (Start, Ende) kein Ende?

305
>>> range(1,11)

gibt Ihnen

[1,2,3,4,5,6,7,8,9,10]

Warum nicht 1-11?

Haben sie sich einfach dazu entschlossen, es zufällig zu machen, oder hat es einen Wert, den ich nicht sehe?

MetaGuru
quelle
11
Lesen Sie Dijkstra, ewd831
SilentGhost
11
Grundsätzlich wählen Sie einen Satz von einzelnen Fehlern für einen anderen aus. Ein Satz führt eher dazu, dass Ihre Schleifen vorzeitig beendet werden, der andere verursacht wahrscheinlich eine Ausnahme (oder einen Pufferüberlauf in anderen Sprachen). Sobald Sie eine Reihe von Code geschrieben haben, werden Sie feststellen, dass die Wahl des Verhaltens range()viel häufiger Sinn macht
John La Rooy
32
Link zu Dijkstra, ewd831: cs.utexas.edu/users/EWD/ewd08xx/EWD831.PDF
unutbu
35
@unutbu Dieser Djikstra-Artikel wird in diesem Thema oft zitiert, bietet aber hier nichts Wertvolles. Die Leute benutzen ihn nur als Appell an die Autorität. Der einzige relevante Pseudo-Grund, den er für die Frage des OP angibt, ist, dass er das Gefühl hat, dass das Einbeziehen der Obergrenze in dem speziellen Fall, in dem die Sequenz leer ist, "unnatürlich" und "hässlich" wird - das ist eine völlig subjektive Position und leicht zu argumentieren, es bringt also nicht viel auf den Tisch. Das "Experiment" mit Mesa ist auch ohne Kenntnis ihrer besonderen Einschränkungen oder Bewertungsmethoden nicht von großem Wert.
Sundar - Reinstate Monica
6
@andreasdr Aber selbst wenn das kosmetische Argument gültig ist, führt Pythons Ansatz nicht zu einem neuen Problem der Lesbarkeit? Im gebräuchlichen Englisch impliziert der Begriff "Bereich", dass etwas von etwas zu etwas reicht - wie ein Intervall. Dass len (Liste (Bereich (1,2))) 1 und len (Liste (Bereich (2))) 2 zurückgibt, muss man wirklich lernen zu verdauen.
Armin

Antworten:

245

Weil es üblicher ist, aufzurufen, range(0, 10)welche Rückgabe [0,1,2,3,4,5,6,7,8,9]10 Elemente enthält, die gleich sindlen(range(0, 10)) . Denken Sie daran, dass Programmierer eine 0-basierte Indizierung bevorzugen.

Beachten Sie auch das folgende allgemeine Code-Snippet:

for i in range(len(li)):
    pass

Könnten Sie sehen, dass dies problematisch wäre , wenn range()genau len(li)das erreicht würde? Der Programmierer müßte explizit subtrahiert 1. Dies auch die gemeinsame Entwicklung von Programmierern folgt lieber for(int i = 0; i < 10; i++)überfor(int i = 0; i <= 9; i++) .

Wenn Sie häufig den Bereich mit einem Start von 1 aufrufen, möchten Sie möglicherweise Ihre eigene Funktion definieren:

>>> def range1(start, end):
...     return range(start, end+1)
...
>>> range1(1, 10)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Moinudin
quelle
48
Wenn das die Begründung wäre, wären die Parameter nicht range(start, count)?
Mark Ransom
3
@shogun Der Startwert ist standardmäßig 0, dh range(10)entspricht range(0, 10).
Moinudin
4
Sie arbeiten range1nicht mit Bereichen, die eine andere Schrittgröße als haben 1.
dimo414
6
Sie erklären, dass der Bereich (x) mit 0 beginnen sollte und x die "Länge des Bereichs" ist. OK. Sie haben jedoch nicht erklärt, warum der Bereich (x, y) mit x beginnen und mit y-1 enden sollte. Wenn der Programmierer eine for-Schleife mit i im Bereich von 1 bis 3 möchte, muss er explizit 1 hinzufügen. Geht es hier wirklich um Bequemlichkeit?
Armin
7
for i in range(len(li)):ist eher ein Antimuster. Man sollte verwenden enumerate.
Hans
27

Obwohl es hier einige nützliche algorithmische Erklärungen gibt, denke ich, dass es hilfreich sein kann, einige einfache Argumente aus dem wirklichen Leben hinzuzufügen, warum dies so funktioniert, die ich als nützlich empfunden habe, als ich jungen Neulingen das Thema vorstellte:

Bei so etwas wie "Bereich (1,10)" kann Verwirrung entstehen, wenn man denkt, dass ein Parameterpaar den "Anfang und das Ende" darstellt.

Es ist eigentlich Start und "Stop".

Wenn es nun der "End" -Wert wäre, könnten Sie erwarten, dass diese Zahl als letzter Eintrag in die Sequenz aufgenommen wird. Aber es ist nicht das "Ende".

Andere nennen diesen Parameter fälschlicherweise "count", denn wenn Sie immer nur 'range (n)' verwenden, wird er natürlich 'n' Mal wiederholt. Diese Logik bricht zusammen, wenn Sie den Startparameter hinzufügen.

Der wichtigste Punkt ist also, sich an den Namen zu erinnern: " stop ". Dies bedeutet, dass an diesem Punkt die Iteration sofort beendet wird, wenn sie erreicht ist. Nicht danach diesem Punkt.

Während "Start" tatsächlich den ersten Wert darstellt, der eingeschlossen werden soll, "bricht" es beim Erreichen des "Stopp" -Werts, anstatt "auch diesen" vor dem Stoppen weiter zu verarbeiten.

Eine Analogie, die ich verwendet habe, um Kindern dies zu erklären, ist, dass es sich ironischerweise besser benimmt als Kinder! Es hört nicht auf, nachdem es sollte - es hört sofort auf, ohne zu beenden, was es tat. (Sie bekommen das;))

Eine andere Analogie - wenn Sie ein Auto fahren, kommen Sie nicht vorbei Stopp- / Ertrags- / Nachgiebigkeitsschild und landen irgendwo neben oder hinter Ihrem Auto. Technisch gesehen haben Sie es immer noch nicht erreicht, wenn Sie aufhören. Es ist nicht in den "Dingen, die Sie auf Ihrer Reise passiert haben" enthalten.

Ich hoffe, einiges davon hilft bei der Erklärung von Pythonitos / Pythonitas!

Dingles
quelle
Diese Erklärung ist intuitiver. Danke
Fred
Die Erklärung der Kinder ist einfach lustig!
Antony Hatchkins
1
Sie versuchen, einem Schwein Lippenstift zu geben. Die Unterscheidung zwischen "Stopp" und "Ende" ist absurd. Wenn ich von 1 auf 7 gehe, habe ich 7 nicht bestanden. Es ist einfach ein Fehler von Python, unterschiedliche Konventionen für Start- und Stopppositionen zu haben. In anderen Sprachen, einschließlich der menschlichen, bedeutet "von X nach Y" "von X nach Y". In Python bedeutet "X: Y" "X: Y-1". Wenn Sie ein Meeting von 9 bis 11 haben, sagen Sie den Leuten, dass es von 9 bis 12 oder von 8 bis 11 ist?
bzip2
24

Exklusive Sortimente haben einige Vorteile:

Zum einen ist jedes Element range(0,n)ein gültiger Index für Längenlisten n.

Hat range(0,n)auch eine Länge von n, nicht n+1welche ein inklusiver Bereich würde.

sepp2k
quelle
18

Es funktioniert gut in Kombination mit nullbasierter Indizierung und len(). Wenn Sie beispielsweise 10 Elemente in einer Liste haben x, sind diese mit 0 bis 9 nummeriert. range(len(x))gibt dir 0-9.

Natürlich werden die Leute sagen , es ist mehr Pythonic zu tun for item in xoder for index, item in enumerate(x)nicht for i in range(len(x)).

Das Schneiden funktioniert auch so: foo[1:4]ist die Elemente 1-3 von foo(wobei zu beachten ist, dass Element 1 aufgrund der nullbasierten Indizierung tatsächlich das zweite Element ist). Aus Gründen der Konsistenz sollten beide gleich funktionieren.

Ich stelle es mir vor als: "Die erste Nummer, die Sie wollen, gefolgt von der ersten Nummer, die Sie nicht wollen." Wenn Sie 1-10 wollen, ist die erste Zahl, die Sie nicht wollen, 11, also ist es range(1, 11).

Wenn es in einer bestimmten Anwendung umständlich wird, ist es einfach genug, eine kleine Hilfsfunktion zu schreiben, die dem Endindex und den Aufrufen 1 hinzufügt range().

irgendwie
quelle
1
Stimmen Sie dem Schneiden zu. 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
Vielleicht sollte das Aufzählungsbeispiel lauten for index, item in enumerate(x), um Verwirrung zu vermeiden
Sean
@seans Danke, behoben.
Kindall
12

Es ist auch nützlich, um Bereiche aufzuteilen. range(a,b)kann in range(a, x)und aufgeteilt werden range(x, b), während Sie mit inklusivem Bereich entweder x-1oder schreiben würden x+1. Während Sie Bereiche selten teilen müssen, neigen Sie dazu, Listen ziemlich oft zu teilen. Dies ist einer der Gründe, warum das Schneiden einer Liste l[a:b]das a-te Element enthält, nicht jedoch das b-te. Dann rangemacht es die gleiche Eigenschaft schön konsistent.

Xuanji
quelle
11

Die Länge des Bereichs ist der obere Wert minus der untere Wert.

Es ist sehr ähnlich zu etwas wie:

for (var i = 1; i < 11; i++) {
    //i goes from 1 to 10 in here
}

in einer Sprache im C-Stil.

Auch wie Rubys Sortiment:

1...11 #this is a range from 1 to 10

Ruby erkennt jedoch, dass Sie den Terminalwert häufig einschließen möchten, und bietet die alternative Syntax:

1..10 #this is also a range from 1 to 10
Skilldrick
quelle
17
Gah! Ich benutze Ruby - nicht, aber ich kann mir vorstellen , dass 1..10vs 1...10schwer sein , zu unterscheiden , beim Lesen Code!
Moinudin
6
@ Marcog - wenn Sie wissen, dass die beiden Formen existieren, stimmen sich Ihre Augen auf den Unterschied ein :)
Skilldrick
11
Rubys Range Operator ist vollkommen intuitiv. Je länger die Form, desto kürzer die Sequenz. Husten
Russell Borogove
4
@Russell, vielleicht sollte 1 ............ 20 den gleichen Bereich wie 1..10 ergeben. Das wäre ein syntaktischer Zucker, für den es sich zu wechseln lohnt. ;)
Kevpie
4
@ Russell Der zusätzliche Punkt drückt den letzten Gegenstand aus dem Bereich :)
Skilldrick
5

Grundsätzlich können wir in Python- range(n)Iterationszeiten n, die exklusiver Natur sind, keinen letzten Wert angeben, wenn sie gedruckt werden. Wir können eine Funktion erstellen, die einen Inklusivwert angibt. Dies bedeutet, dass auch der zuletzt im Bereich genannte Wert gedruckt wird.

def main():
    for i in inclusive_range(25):
        print(i, sep=" ")


def inclusive_range(*args):
    numargs = len(args)
    if numargs == 0:
        raise TypeError("you need to write at least a value")
    elif numargs == 1:
        stop = args[0]
        start = 0
        step = 1
    elif numargs == 2:
        (start, stop) = args
        step = 1
    elif numargs == 3:
        (start, stop, step) = args
    else:
        raise TypeError("Inclusive range was expected at most 3 arguments,got {}".format(numargs))
    i = start
    while i <= stop:
        yield i
        i += step


if __name__ == "__main__":
    main()
Ashish Dixit
quelle
4

Betrachten Sie den Code

for i in range(10):
    print "You'll see this 10 times", i

Die Idee ist, dass Sie eine Liste der Länge erhalten y-x , die Sie (wie Sie oben sehen) durchlaufen können.

Informieren Sie sich in den Python-Dokumenten über den Bereich - sie betrachten die For-Loop-Iteration als primären Verwendungszweck.

Robert
quelle
1

In vielen Fällen ist es einfach bequemer, darüber nachzudenken.

Grundsätzlich können wir uns einen Bereich als Intervall zwischen startund vorstellen end. Wenn start <= end, ist die Länge des Intervalls zwischen ihnen end - start. Wenn lentatsächlich als Länge definiert wurde, hätten Sie:

len(range(start, end)) == start - end

Wir zählen jedoch die im Bereich enthaltenen Ganzzahlen, anstatt die Länge des Intervalls zu messen. Um die obige Eigenschaft beizubehalten, sollten wir einen der Endpunkte einschließen und den anderen ausschließen.

Das Hinzufügen des stepParameters entspricht dem Einfügen einer Längeneinheit. In diesem Fall würden Sie erwarten

len(range(start, end, step)) == (start - end) / step

für die Länge. Um die Zählung zu erhalten, verwenden Sie einfach die Ganzzahldivision.

Arseny
quelle
Diese Abwehrmechanismen gegen Pythons Inkonsistenz sind komisch. Wenn ich das Intervall zwischen zwei Zahlen haben wollte, warum sollte ich dann die Subtraktion verwenden, um die Differenz anstelle des Intervalls zu erhalten? Es ist inkonsistent, unterschiedliche Indexierungskonventionen für Start- und Endpositionen zu verwenden. Warum sollten Sie "5:22" schreiben müssen, um die Positionen 5 bis 21 zu erhalten?
bzip2
Es ist nicht Pythons, es ist auf der ganzen Linie ziemlich verbreitet. In C, Java, Ruby nennen Sie es
Arseny
Ich wollte damit sagen, dass es für die Indizierung üblich ist, nicht dass die anderen Sprachen notwendigerweise genau die gleiche Art von Objekt haben
Arseny