Perché "1000000000000000 nel range (1000000000000001)" è così veloce in Python 3?


2116

Comprendo che la range()funzione, che in realtà è un tipo di oggetto in Python 3 , genera il suo contenuto al volo, simile a un generatore.

Stando così le cose, mi sarei aspettato che la seguente riga impiegasse una quantità eccessiva di tempo, perché per determinare se 1 quadrilione è compreso nell'intervallo, sarebbe necessario generare un quadrilione di valori:

1000000000000000 in range(1000000000000001)

Inoltre: sembra che non importa quanti zeri aggiungo, il calcolo richiede più o meno la stessa quantità di tempo (sostanzialmente istantaneo).

Ho anche provato cose del genere, ma il calcolo è ancora quasi istantaneo:

1000000000000000000000 in range(0,1000000000000000000001,10) # count by tens

Se provo ad implementare la mia funzione di intervallo, il risultato non è così bello !!

def my_crappy_range(N):
    i = 0
    while i < N:
        yield i
        i += 1
    return

Cosa sta range()facendo l' oggetto sotto il cofano che lo rende così veloce?


La risposta di Martijn Pieters è stata scelta per la sua completezza, ma vedi anche la prima risposta di abarnert per una buona discussione su cosa significhi rangeessere una sequenza a tutti gli effetti in Python 3 e alcune informazioni / avvertenze riguardo a potenziali incoerenze per__contains__ ottimizzazione delle funzioni tra le implementazioni di Python . L'altra risposta di abarnert va in qualche dettaglio in più e fornisce collegamenti per coloro che sono interessati alla storia dietro l'ottimizzazione in Python 3 (e la mancanza di ottimizzazione xrangein Python 2). Le risposte di poke e di wim forniscono il codice sorgente C e le spiegazioni pertinenti per coloro che sono interessati.


70
Si noti che questo è il caso solo se l'oggetto che stiamo controllando è un boolo longtipo, con altri tipi di oggetto diventerà pazzo. Prova con:100000000000000.0 in range(1000000000000001)
Ashwini Chaudhary,

10
Chi ti ha detto che rangeè un generatore?
abarnert,

7
@abarnert Penso che la modifica che ho apportato abbia lasciato intatta la confusione.
Rick supporta Monica

5
@AshwiniChaudhary non è Python2 xrangeuguale a Python3range ?
Superbo

28
Gli xrange()oggetti @Superbest non hanno alcun __contains__metodo, quindi il controllo degli articoli deve passare in rassegna tutti gli articoli. Inoltre ci sono alcune altre modifiche range(), come supporta lo slicing (che restituisce di nuovo un rangeoggetto) e ora ha anche counte indexmetodi per renderlo compatibile con collections.SequenceABC.
Ashwini Chaudhary,

Risposte:


2171

L' range()oggetto Python 3 non produce immediatamente numeri; è un oggetto sequenza intelligente che produce numeri su richiesta . Tutto ciò che contiene sono i valori di inizio, fine e step, quindi man mano che esegui l'iterazione sull'oggetto, viene calcolato ogni intero successivo.

L'oggetto implementa anche l' object.__contains__hook e calcola se il tuo numero fa parte del suo intervallo. Il calcolo è un'operazione a tempo (quasi) costante * . Non è mai necessario scansionare tutti i possibili numeri interi nell'intervallo.

Dalla range()documentazione dell'oggetto :

Il vantaggio del rangetipo sopra un normale listo tupleè che un oggetto intervallo impiega sempre lo stesso (piccola) quantità di memoria, non importa la dimensione dell'intervallo che rappresenta (come memorizza solo i start, stope stepvalori, calcolando singoli elementi e sottointervalli come necessario).

Quindi, come minimo, il tuo range()oggetto farebbe:

class my_range(object):
    def __init__(self, start, stop=None, step=1):
        if stop is None:
            start, stop = 0, start
        self.start, self.stop, self.step = start, stop, step
        if step < 0:
            lo, hi, step = stop, start, -step
        else:
            lo, hi = start, stop
        self.length = 0 if lo > hi else ((hi - lo - 1) // step) + 1

    def __iter__(self):
        current = self.start
        if self.step < 0:
            while current > self.stop:
                yield current
                current += self.step
        else:
            while current < self.stop:
                yield current
                current += self.step

    def __len__(self):
        return self.length

    def __getitem__(self, i):
        if i < 0:
            i += self.length
        if 0 <= i < self.length:
            return self.start + i * self.step
        raise IndexError('Index out of range: {}'.format(i))

    def __contains__(self, num):
        if self.step < 0:
            if not (self.stop < num <= self.start):
                return False
        else:
            if not (self.start <= num < self.stop):
                return False
        return (num - self.start) % self.step == 0

Mancano ancora molte cose che un vero range()supporto (come i metodi .index()o .count(), hashing, test di uguaglianza o slicing), ma dovrebbero darti un'idea.

Ho anche semplificato l' __contains__implementazione per concentrarmi solo sui test interi; se si assegna a un range()oggetto reale un valore non intero (comprese le sottoclassi di int), viene avviata una scansione lenta per verificare se esiste una corrispondenza, proprio come se si utilizzasse un test di contenimento su un elenco di tutti i valori contenuti. Ciò è stato fatto per continuare a supportare altri tipi numerici che supportano il test di uguaglianza con numeri interi ma che non dovrebbero supportare anche l'aritmetica dei numeri interi. Vedi il problema originale di Python che ha implementato il test di contenimento.


* Tempo quasi costante perché gli interi Python sono illimitati e quindi anche le operazioni matematiche crescono nel tempo man mano che N cresce, rendendola un'operazione O (log N). Dato che è tutto eseguito in codice C ottimizzato e Python memorizza i valori interi in blocchi di 30 bit, si esaurirebbe la memoria prima di vedere qualsiasi impatto sulle prestazioni a causa delle dimensioni degli interi coinvolti qui.


58
Curiosità: poiché hai un'implementazione funzionante di __getitem__e __len__, l' __iter__implementazione in realtà non è necessaria.
Lucretiel,

2
@Lucretiel: in Python 2.3 , è xrangeiteratorstato aggiunto uno speciale proprio perché non abbastanza veloce. E poi da qualche parte in 3.x (non sono sicuro che fosse 3.0 o 3.2) è stato lanciato e usano lo stesso listiteratortipo che listusa.
abarnert,

1
Definirei il costruttore come def __init__(self, *start_stop_step)e lo analizzerei da lì; il modo in cui gli argomenti sono ora etichettati ora sono un po 'confusi. Tuttavia, +1; hai ancora sicuramente spiegato il comportamento.
Cody Piersall,

1
@CodyPiersall: Sfortunatamente, questa è la firma dell'inizializzatore della classe reale. rangeè più vecchio di *args(molto meno l' argclinicAPI che consente alle funzioni C-API di avere firme Python complete). Alcune altre vecchie funzioni (e alcune nuove funzioni, come xrange, slicee itertools.islice, per coerenza) funzionano allo stesso modo, ma per la maggior parte, Guido e gli altri sviluppatori principali sembrano concordare con te. I documenti 2.0+ descrivono persino rangee amici come se fossero sovraccarichi in stile C ++ piuttosto che mostrare la firma confusa effettiva.
Abarnert,

2
@CodyPiersall: In realtà, ecco una citazione di Guido nella argclinicdiscussione, quando Nick Coghlan ha trovato un modo per consentire di definire in rangemodo inequivocabile: "Per favore, non rendere più facile per le persone copiare la mia peggiore decisione di progettazione." Quindi, sono abbastanza sicuro che sia d'accordo che rangesia confuso come scritto.
Abarnert,

845

Il fondamentale fraintendimento qui è nel pensare che rangesia un generatore. Non è. In realtà, non è alcun tipo di iteratore.

Puoi dirlo abbastanza facilmente:

>>> a = range(5)
>>> print(list(a))
[0, 1, 2, 3, 4]
>>> print(list(a))
[0, 1, 2, 3, 4]

Se fosse un generatore, iterarlo una volta lo esaurirebbe:

>>> b = my_crappy_range(5)
>>> print(list(b))
[0, 1, 2, 3, 4]
>>> print(list(b))
[]

Ciò che rangerealmente è, è una sequenza, proprio come un elenco. Puoi persino provare questo:

>>> import collections.abc
>>> isinstance(a, collections.abc.Sequence)
True

Ciò significa che deve seguire tutte le regole per essere una sequenza:

>>> a[3]         # indexable
3
>>> len(a)       # sized
5
>>> 3 in a       # membership
True
>>> reversed(a)  # reversible
<range_iterator at 0x101cd2360>
>>> a.index(3)   # implements 'index'
3
>>> a.count(3)   # implements 'count'
1

La differenza tra a rangee a listè che a rangeè una sequenza pigra o dinamica ; essa non ricorda tutti i suoi valori, solo ricorda la sua start, stope step, e crea i valori su richiesta su __getitem__.

(Come nota print(iter(a))a margine, noterai che rangeutilizza lo stesso listiteratortipo di list. Come funziona? A listiteratornon usa nulla di speciale a listparte il fatto che fornisce un'implementazione in C __getitem__, quindi funziona bene per rangepure.)


Ora, non c'è nulla che dica che Sequence.__contains__deve essere un tempo costante - in effetti, per ovvi esempi di sequenze come list, non lo è. Ma non c'è nulla che dica che non può essere. Ed è più facile da implementare range.__contains__controllandolo matematicamente ( (val - start) % stepma con una certa complessità in più per gestire i passaggi negativi) piuttosto che generare e testare tutti i valori, quindi perché non dovrebbe farlo nel modo migliore?

Ma non sembra esserci nulla nella lingua che garantisca che ciò accada. Come sottolinea Ashwini Chaudhari, se gli dai un valore non integrale, invece di convertirlo in intero e fare il test matematico, tornerà a iterare tutti i valori e confrontarli uno per uno. E solo perché le versioni di CPython 3.2+ e PyPy 3.x contengono questa ottimizzazione, ed è una buona idea ovvia e facile da fare, non c'è motivo per cui IronPython o NewKickAssPython 3.x non possano lasciarlo fuori. (E in effetti CPython 3.0-3.1 non lo includeva.)


Se rangeeffettivamente fosse un generatore, my_crappy_rangenon avrebbe senso testarlo in __contains__questo modo, o almeno il modo in cui ha senso non sarebbe ovvio. Se avessi già ripetuto i primi 3 valori, è 1ancora inil generatore? Il testing per 1causare iterazione e consumo di tutti i valori fino a 1(o fino al primo valore >= 1)?


10
Questa è una cosa abbastanza importante da chiarire. Suppongo che le differenze tra Python 2 e 3 possano aver portato alla mia confusione su questo punto. In ogni caso, avrei dovuto rendermene conto poiché rangeè elencato (insieme a liste tuple) come tipo di sequenza .
Rick supporta Monica

4
@RickTeachey: In realtà, in 2.6+ (penso; forse 2.5+), xrangeè anche una sequenza. Vedi 2.7 documenti . In effetti, è sempre stata una sequenza quasi.
abarnert,

5
@RickTeachey: In realtà, mi sbagliavo; in 2.6-2.7 (e 3.0-3.1), afferma di essere una sequenza, ma è ancora solo una quasi sequenza. Vedi la mia altra risposta.
abarnert,

2
Non è un iteratore, è una sequenza (Iterabile in termini di Java, IEnumerable di C #) - qualcosa con un .__iter__()metodo che restituirà un iteratore. A sua volta può essere utilizzato solo una volta.
Smit Johnth,

4
@ThomasAhle: Perché rangenon controlla i tipi quando non è un numero intero, poiché è sempre possibile che un tipo abbia un carattere __eq__compatibile con int. Certo, strovviamente non funzionerà, ma non volevano rallentare le cose controllando esplicitamente tutti i tipi che non possono essere presenti (e dopotutto, una strsottoclasse potrebbe sovrascrivere __eq__ed essere contenuta in range).
ShadowRanger,

377

Usa la fonte , Luke!

In CPython, range(...).__contains__ (un wrapper di metodo) verrà infine delegato a un semplice calcolo che verifica se il valore può essere compreso nell'intervallo. La ragione della velocità qui sta usando un ragionamento matematico sui limiti, piuttosto che un'iterazione diretta dell'oggetto range . Per spiegare la logica utilizzata:

  1. Verificare che il numero sia compreso tra start e stop, e
  2. Verifica che il valore del passo non "passi" sul nostro numero.

Ad esempio, 994è range(4, 1000, 2)perché:

  1. 4 <= 994 < 1000, e
  2. (994 - 4) % 2 == 0.

Di seguito è incluso il codice C completo, che è un po 'più dettagliato a causa della gestione della memoria e dei dettagli del conteggio dei riferimenti, ma l'idea di base è lì:

static int
range_contains_long(rangeobject *r, PyObject *ob)
{
    int cmp1, cmp2, cmp3;
    PyObject *tmp1 = NULL;
    PyObject *tmp2 = NULL;
    PyObject *zero = NULL;
    int result = -1;

    zero = PyLong_FromLong(0);
    if (zero == NULL) /* MemoryError in int(0) */
        goto end;

    /* Check if the value can possibly be in the range. */

    cmp1 = PyObject_RichCompareBool(r->step, zero, Py_GT);
    if (cmp1 == -1)
        goto end;
    if (cmp1 == 1) { /* positive steps: start <= ob < stop */
        cmp2 = PyObject_RichCompareBool(r->start, ob, Py_LE);
        cmp3 = PyObject_RichCompareBool(ob, r->stop, Py_LT);
    }
    else { /* negative steps: stop < ob <= start */
        cmp2 = PyObject_RichCompareBool(ob, r->start, Py_LE);
        cmp3 = PyObject_RichCompareBool(r->stop, ob, Py_LT);
    }

    if (cmp2 == -1 || cmp3 == -1) /* TypeError */
        goto end;
    if (cmp2 == 0 || cmp3 == 0) { /* ob outside of range */
        result = 0;
        goto end;
    }

    /* Check that the stride does not invalidate ob's membership. */
    tmp1 = PyNumber_Subtract(ob, r->start);
    if (tmp1 == NULL)
        goto end;
    tmp2 = PyNumber_Remainder(tmp1, r->step);
    if (tmp2 == NULL)
        goto end;
    /* result = ((int(ob) - start) % step) == 0 */
    result = PyObject_RichCompareBool(tmp2, zero, Py_EQ);
  end:
    Py_XDECREF(tmp1);
    Py_XDECREF(tmp2);
    Py_XDECREF(zero);
    return result;
}

static int
range_contains(rangeobject *r, PyObject *ob)
{
    if (PyLong_CheckExact(ob) || PyBool_Check(ob))
        return range_contains_long(r, ob);

    return (int)_PySequence_IterSearch((PyObject*)r, ob,
                                       PY_ITERSEARCH_CONTAINS);
}

La "carne" dell'idea è menzionata nella riga :

/* result = ((int(ob) - start) % step) == 0 */ 

Come nota finale, osserva la range_containsfunzione nella parte inferiore dello snippet di codice. Se l'esatto controllo del tipo fallisce, allora non usiamo l'algoritmo intelligente descritto, ma ricadiamo in una stupida ricerca di iterazione dell'intervallo usando _PySequence_IterSearch! Puoi controllare questo comportamento nell'interprete (sto usando v3.5.0 qui):

>>> x, r = 1000000000000000, range(1000000000000001)
>>> class MyInt(int):
...     pass
... 
>>> x_ = MyInt(x)
>>> x in r  # calculates immediately :) 
True
>>> x_ in r  # iterates for ages.. :( 
^\Quit (core dumped)

144

Per aggiungere alla risposta di Martijn, questa è la parte rilevante della fonte (in C, poiché l'oggetto range è scritto in codice nativo):

static int
range_contains(rangeobject *r, PyObject *ob)
{
    if (PyLong_CheckExact(ob) || PyBool_Check(ob))
        return range_contains_long(r, ob);

    return (int)_PySequence_IterSearch((PyObject*)r, ob,
                                       PY_ITERSEARCH_CONTAINS);
}

Quindi, per gli PyLongoggetti (che è intin Python 3), userà ilrange_contains_long funzione per determinare il risultato. E quella funzione controlla essenzialmente se si obtrova nell'intervallo specificato (anche se sembra un po 'più complesso in C).

Se non è un int oggetto, torna all'iterazione fino a quando non trova il valore (o meno).

L'intera logica potrebbe essere tradotta in pseudo-Python in questo modo:

def range_contains (rangeObj, obj):
    if isinstance(obj, int):
        return range_contains_long(rangeObj, obj)

    # default logic by iterating
    return any(obj == x for x in rangeObj)

def range_contains_long (r, num):
    if r.step > 0:
        # positive step: r.start <= num < r.stop
        cmp2 = r.start <= num
        cmp3 = num < r.stop
    else:
        # negative step: r.start >= num > r.stop
        cmp2 = num <= r.start
        cmp3 = r.stop < num

    # outside of the range boundaries
    if not cmp2 or not cmp3:
        return False

    # num must be on a valid step inside the boundaries
    return (num - r.start) % r.step == 0

11
@ChrisWesseling: penso che questa sia un'informazione abbastanza diversa (e abbastanza) che la modifica della risposta di Martijn non sarebbe stata appropriata qui. È un appello al giudizio, ma le persone di solito sbagliano per non apportare cambiamenti drastici alle risposte degli altri.
abarnert,

105

Se ti stai chiedendo perché questa ottimizzazione è stata aggiunta range.__contains__e perché non è stata aggiunta xrange.__contains__in 2.7:

Innanzitutto, come ha scoperto Ashwini Chaudhary, il numero 1766304 è stato aperto esplicitamente per ottimizzare [x]range.__contains__. Una patch per questo è stata accettata e registrata per 3.2 , ma non riportata a 2.7 perché "xrange si è comportato in questo modo per così tanto tempo che non vedo cosa ci compra per impegnare la patch così tardi." (2.7 era quasi fuori a quel punto.)

Nel frattempo:

Originariamente, xrange era un oggetto non del tutto in sequenza. Come dicono i documenti 3.1 :

Gli oggetti intervallo hanno un comportamento molto limitato: supportano solo indicizzazione, iterazione e lenfunzione.

Questo non era del tutto vero; un xrangeoggetto ha effettivamente supportato alcune altre cose che vengono automaticamente con l'indicizzazione e len, * incluso __contains__(tramite la ricerca lineare). Ma nessuno pensava che valesse la pena renderle sequenze complete al momento.

Quindi, come parte dell'implementazione del PEP delle classi di base astratte , era importante capire quali tipi predefiniti dovrebbero essere contrassegnati come implementanti quali ABC e xrange/ ha rangeaffermato di implementare collections.Sequence, anche se gestiva ancora lo stesso "comportamento molto scarso". Nessuno ha notato questo problema fino al numero 9213 . La patch per quel problema non solo ha aggiunto indexe count3.2 range, ha anche rielaborato l'ottimizzato __contains__(che condivide la stessa matematica con index, ed è direttamente utilizzato da count). ** Anche questa modifica è stata introdotta per 3.2 e non è stata trasferita su 2.x perché "è un bugfix che aggiunge nuovi metodi". (A questo punto, 2.7 era già passato allo stato rc.)

Quindi, c'erano due possibilità per riportare questa ottimizzazione a 2.7, ma sono stati entrambi respinti.


* In effetti, puoi persino ottenere l'iterazione gratuitamente solo con l'indicizzazione, ma in 2.3 gli xrange oggetti hanno un iteratore personalizzato.

** La prima versione l'ha effettivamente reimplementata e ha sbagliato i dettagli, ad esempio, ti darebbe MyIntSubclass(2) in range(5) == False. Ma la versione aggiornata della patch di Daniel Stutzbach ha ripristinato la maggior parte del codice precedente, incluso il fallback al generico, lento _PySequence_IterSearchche il pre-3.2 range.__contains__utilizzava implicitamente quando l'ottimizzazione non si applica.


4
Dai commenti qui: migliorarexrange.__contains__ , sembra che non abbiano eseguito il backport su Python 2 solo per lasciare un elemento di sorpresa per gli utenti ed era troppo tardi o_O. La patchcount e è stata aggiunta in seguito. File in quel momento: hg.python.org/cpython/file/d599a3f2e72d/Objects/rangeobject.cindex
Ashwini Chaudhary

12
Ho il sospetto sinistro che alcuni sviluppatori core di Python siano parziali di "amore duro" per Python 2.x perché vogliono incoraggiare le persone a passare al Python3 di gran lunga superiore :)
wim

4
Inoltre scommetto che è un enorme onere dover aggiungere nuove funzionalità alle vecchie versioni. Immagina se andassi a Oracle e dicessi: "Guarda, sono su Java 1.4 e mi merito espressioni lambda! Esegui il backport per niente".
Rob Grant,

2
@RickTeachey sì, è solo un esempio. Se avessi detto 1.7 sarebbe ancora valido. È una differenza quantitativa non qualitativa. Fondamentalmente gli sviluppatori (non pagati) non possono sempre creare cose interessanti in 3.xe riportarle in 2.x per coloro che non vogliono aggiornare. È un peso enorme e ridicolo. Pensi che ci sia ancora qualcosa di sbagliato nel mio ragionamento?
Rob Grant,

3
@RickTeachey: 2.7 era tra 3.1 e 3.2, non intorno a 3.3. Ciò significa che la 2.7 era in rc quando sono state apportate le ultime modifiche alla 3.2, il che rende più semplice la comprensione dei commenti sui bug. Ad ogni modo, penso che abbiano fatto alcuni errori in retrospettiva (soprattutto supponendo che le persone migrerebbero 2to3invece che tramite il codice a doppia versione con l'aiuto di biblioteche come six, motivo per cui abbiamo avuto cose del genere dict.viewkeysche nessuno avrebbe mai usato), e c'erano alcuni cambiamenti che sono arrivati ​​troppo tardi nella 3.2, ma per la maggior parte 2.7 è stata una versione "impressionante 2.x di sempre" piuttosto impressionante.
Abarnert,

47

Le altre risposte lo hanno già spiegato bene, ma vorrei offrire un altro esperimento che illustra la natura degli oggetti range:

>>> r = range(5)
>>> for i in r:
        print(i, 2 in r, list(r))

0 True [0, 1, 2, 3, 4]
1 True [0, 1, 2, 3, 4]
2 True [0, 1, 2, 3, 4]
3 True [0, 1, 2, 3, 4]
4 True [0, 1, 2, 3, 4]

Come puoi vedere, un oggetto range è un oggetto che ricorda il suo range e può essere usato molte volte (anche se iterando su di esso), non solo un generatore una tantum.


27

Si tratta di un approccio pigro alla valutazione e di un'ulteriore ottimizzazione di range. I valori negli intervalli non devono essere calcolati fino a quando non vengono utilizzati o addirittura ulteriormente a causa di un'ulteriore ottimizzazione.

A proposito, il tuo numero intero non è così grande, considera sys.maxsize

sys.maxsize in range(sys.maxsize) è abbastanza veloce

grazie all'ottimizzazione - è facile confrontare un dato numero intero con il minimo e il massimo dell'intervallo.

ma:

Decimal(sys.maxsize) in range(sys.maxsize) è piuttosto lento .

(in questo caso, non c'è ottimizzazione in range, quindi se python riceve un decimale imprevisto, python confronterà tutti i numeri)

È necessario essere consapevoli di un dettaglio dell'implementazione, ma non si deve fare affidamento su di esso, perché ciò potrebbe cambiare in futuro.


4
Fai attenzione a numeri interi grandi e mobili. Sulla maggior parte delle macchine, float(sys.maxsize) != sys.maxsize)anche se sys.maxsize-float(sys.maxsize) == 0.
holdenweb,

18

TL; DR

L'oggetto restituito da range()è in realtà unrange oggetto. Questo oggetto implementa l'interfaccia iteratore in modo da poter iterare in sequenza sui suoi valori, proprio come un generatore, un elenco o una tupla.

Ma implementa anche l' __contains__interfaccia che è in realtà ciò che viene chiamato quando un oggetto appare sul lato destro indell'operatore. Il __contains__()metodo restituisce a boolof se l'oggetto sul lato sinistro di inè nell'oggetto. Poiché gli rangeoggetti conoscono i loro limiti e il passo, questo è molto facile da implementare in O (1).


0
  1. Grazie all'ottimizzazione, è molto semplice confrontare determinati numeri interi solo con l'intervallo minimo e massimo.
  2. La ragione per cui la funzione range () è così veloce in Python3 è che qui usiamo il ragionamento matematico per i limiti, piuttosto che un'iterazione diretta dell'oggetto range.
  3. Quindi, per spiegare la logica qui:
    • Controllare se il numero è compreso tra l'inizio e l'arresto.
    • Controlla se il valore di precisione del passo non supera il nostro numero.
  4. Prendiamo un esempio, 997 è nel range (4, 1000, 3) perché:

    4 <= 997 < 1000, and (997 - 4) % 3 == 0.


1
Puoi condividere la fonte per questo? Anche se sembra legittimo, sarebbe bene appoggiare queste affermazioni con il codice reale
Nico Haase,

Penso che questo sia un esempio che potrebbe essere implementato. Non è esattamente il modo in cui è implementato. Sebbene non sia stato fornito alcun riferimento, è un buon suggerimento sufficiente per capire perché il controllo dell'inclusione per intervallo può essere molto più veloce dell'elenco o della tupla
Mohammed Shareef C

0

Prova valori di x-1 in (i for i in range(x))grandi dimensioni x, che utilizzano una comprensione del generatore per evitare di invocare l' range.__contains__ottimizzazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.