2015-05-21 11 views
21

Ho fatto una funzione po generatore per intervalli di caratteri:Come estendere, imitare o emulare la funzione di intervallo?

>>> def crange(start, end): 
...  for i in range(ord(start), ord(end)+1): 
...    yield chr(i) 
... 

e poi posso fare questo:

>>> print(*crange('a','e')) 
a b c d e 

Yay! Ma questo non funziona:

>>> crange('a','e')[::2] 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: 'generator' object is not subscriptable 

E questo funziona, ma è O (n), a differenza di range s' O (1):

>>> 'y' in crange('a','z') 
True 

Ciò significa che occorrono circa 0,35 secondi per la ricerca per carattere numero 109.999 su un massimo di 110.000. 109999 in range(110000) è, ovviamente, veloce.

A quel punto, il mio primo pensiero era semplicemente la sottoclasse dell'intervallo. Purtroppo:

>>> class A(range): 
...  pass 
... 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: type 'range' is not an acceptable base type 

Quindi penso che avrei dovuto imitare in qualche modo che mi permette di passare personaggi come argomenti, funziona come range internamente e produce caratteri. Sfortunatamente, non sono sicuro di come procedere. Ho provato un dir():

>>> print(*dir(range), sep='\n') 
__class__ 
__contains__ 
__delattr__ 
__dir__ 
__doc__ 
__eq__ 
__format__ 
__ge__ 
__getattribute__ 
__getitem__ 
__gt__ 
__hash__ 
__init__ 
__iter__ 
__le__ 
__len__ 
__lt__ 
__ne__ 
__new__ 
__reduce__ 
__reduce_ex__ 
__repr__ 
__reversed__ 
__setattr__ 
__sizeof__ 
__str__ 
__subclasshook__ 
count 
index 
start 
step 
stop 

che mi permette di vedere quali funzioni sono in là, ma non sono sicuro di quello che stanno facendo, o come range li utilizza. Ho cercato la fonte per range, ma è in C, e non so dove trovare il suo wrapper Python (ne ha uno, giusto?).

Dove vado da qui, e dovrei anche andarci?

+0

"ma è in C, e non so dove trovare il suo wrapper Python (ne ha uno, giusto?)" - No. Come 'list' o' dict', nessuna parte di 'range' è scritta in Python. – user2357112

+0

Informazioni sulla classe, forse 'classe A (oggetto, intervallo):' – Zizouz212

+3

Mentre questa potrebbe sembrare una domanda interessante, sto votando per chiuderla come troppo ampia. Non c'è un buon modo per farlo se non passando attraverso l'intera API di 'range' e replicandolo, quindi le risposte dovrebbero descrivere tutto ciò che fa 'range', tutti gli hook usati per personalizzare' len' e slicing e tutto, e loro Prenderò come 10 pagine. Raccomando di cercare i nomi dei metodi e di esaminare il [modello dati Python] (https://docs.python.org/3/reference/datamodel.html). – user2357112

risposta

9

Da aggiungere alla risposta di Martin Konecny. Probabilmente vuoi usare un intervallo interno per tutto e convertire tra chr e ord.

class crange: 
    def __init__(self, *args, **kwargs): 
     args = [ord(arg) for arg in args] 
     kwargs = {key: ord(val) for key, val in kwargs.items()} 
     self.range = range(*args, **kwargs) 

    def __iter__(self): 
     for n in self.range: 
      yield chr(n) 

    def __contains__(self, c): 
     return ord(c) in self.range 

    def __getitem__(self, i): 
     if isinstance(i, slice): 
      ret = crange('\x00') 
      ret.range = self.range[i] 
      return ret 
     else: 
      return chr(self.range[i]) 

    def __repr__(self): 
     return "crange({}, {})".format(
      repr(chr(self.range.start)), repr(chr(self.range.stop))) 

r = crange('a', 'f') 
print(list(r)) 
print('b' in r) 
print('f' in r) 
print(r[:2]) 

In altre parole: se non siamo in grado di ereditare esso possiamo usare object composition.

+0

Stavo per chiedere se sarebbe una buona idea usare internamente un oggetto 'range' ... :) – TigerhawkT3

+1

' self.range .__ contiene __ (ord (c)) '→' ord (c) in self.range '; 'self.range .__ getitem __ (i)' → 'self.range [i]', '('crange (' + repr (chr (self.range.start)) + ',' + repr (chr (self.range .stop)) + ')') '→' "crange ({}, {})". format (self.range.start, self.range.stop) '. – Veedrac

+0

Modifiche suggerite, grazie. Mi sono bloccato a usare il nome del metodo per simmetria. repr era un disastro, nessuna scusa :(. – Ariakenom

17

A quel punto, il mio primo pensiero era semplicemente la sottoclasse dell'intervallo.

range era una funzione in python2 e una classe "finale" in python3 (more info here) - in entrambi i casi, non qualcosa che si può sottoclasse. Dovrai creare una classe crange che si estenda da un object come tipo di base.

class crange(object): 

E questo funziona, ma è O (n), a differenza di O di gamma (1)

In Python 3, c'è un metodo __contains__ che verrà definita per l'oggetto.

Per gli oggetti che non definiscono __contains__(), il test di appartenenza prima tenta di iterazione via __iter__(), poi il vecchio protocollo sequenza di iterazione tramite __getitem__(), vedere questa sezione nella guida di riferimento.

Ciò consente a Python di determinare se il valore è compreso nell'intervallo senza effettivamente elencare l'intervallo.

Per un semplice esempio, se l'intervallo è compreso tra 1 e 1.000.000, è banale determinare se 23546 si trova in tale intervallo (1 < 23546 < 1000000).Naturalmente l'effettiva attuazione è un po 'più complessa e aggiunge capacità di gestire incrementi ecc

quanto riguarda:

Yay! Ma questo non funziona: >>> crange('a','e')[::2]

In questo caso è necessario definire __getitem__ sul vostro oggetto. Ecco un esempio di alcuni dei metodi richiesti:

class crange(object): 
    def __init__(self, start, end, step=1): 
     # initialize your range object 
     self.start = start 
     self.end = end 
     self.step = step 

    def __iter__(self): 
     # enable iteration over your object 
     # (assume step size is 1) 
     for i in range(ord(self.start), ord(self.end)+1): 
      yield chr(i) 

    def __getitem__(self, i): 
     # enable accessing items in your range by index 
     # also enable crange('a','e')[::2] 
     # (assuming step size of 1) 
     if isinstance(i, slice): 
      # implement slicing 
     else: 
      return chr(ord(self.start) + i) 

    def __contains__(self, char): 
     # enable O(1) determination of whether a value is in your range 
     # (assume step size is 1) 
     return ord(self.start) <= ord(char) < ord(self.end) 

    def __len__(self): 
     # return length (assuming step size of 1) 
     return ord(self.end) - ord(self.start) 
+0

Se ricordo, correttamente, '__taintains __()' è implementato in 'range()' come: 'if step == 1: return start <= num

+1

@ DougR.thx Ho compilato alcuni dei metodi e ho usato il metodo '__contains __()' –

+0

Felice di essere d'aiuto. Mi dispiace, mi ci sono voluti tre volte per ottenere il risultato giusto. Questo è il tipo di problema che mi piace vedere qui su SO. –

Problemi correlati