2013-02-16 9 views
5

La libreria standard di molti linguaggi di programmazione include una "API scanner" per estrarre stringhe, numeri o altri oggetti dai flussi di input del testo. (Ad esempio, Java include la classe Scanner, C++ include istream e C include scanf).Python Stream Extraction

Qual è l'equivalente di questo in Python?

Python ha un'interfaccia di flusso, ad esempio classi che ereditano da io.IOBase. Tuttavia, l'interfaccia di flusso Python TextIOBase fornisce solo funzionalità per l'input orientato alla linea. Dopo reading the documentation e searching on Google, non riesco a trovare qualcosa nei moduli standard di Python che mi consenta, ad esempio, di estrarre un intero da un flusso di testo o di estrarre la successiva parola delimitata dallo spazio come stringa. Ci sono delle strutture standard per fare questo?

+0

Se l'input è molto sporco/arbitrario, tendo ad usare il modulo 're'; se l'input è strutturato preferisco una libreria parser come simpleparse (EBNL è più semplice da gestire rispetto alle espressioni regolari). –

+1

Se hai in mente un caso d'uso, forse sarebbe più produttivo aggiornare la tua domanda invece di fare un'inchiesta generica. –

+0

Vedere http://stackoverflow.com/questions/2175080/sscanf-in-python per altri suggerimenti. –

risposta

3

Non vi è alcun equivalente di fscanf o Java Scanner. La soluzione più semplice è quella di richiedere all'utente di utilizzare l'input separato newline anziché l'input separato dallo spazio, quindi è possibile leggere riga per riga e convertire le righe nel tipo corretto.

Se si desidera che l'utente fornisca un input più strutturato, è consigliabile creare un parser per l'input dell'utente. Esistono alcune librerie di analisi per Python, ad esempio pyparsing. Esiste anche un modulo scanf, anche se l'ultimo aggiornamento è del 2008.

Se non si desidera avere dipendenze esterne, è possibile utilizzare espressioni regex per far corrispondere le sequenze di input. Certamente le regex richiedono di lavorare su stringhe, ma puoi facilmente superare questa limitazione leggendo in blocchi. Per esempio qualcosa come questo dovrebbe funzionare bene la maggior parte del tempo:

import re 


FORMATS_TYPES = { 
    'd': int, 
    'f': float, 
    's': str, 
} 


FORMATS_REGEXES = {  
    'd': re.compile(r'(?:\s|\b)*([+-]?\d+)(?:\s|\b)*'), 
    'f': re.compile(r'(?:\s|\b)*([+-]?\d+\.?\d*)(?:\s|\b)*'), 
    's': re.compile(r'\b(\w+)\b'), 
} 


FORMAT_FIELD_REGEX = re.compile(r'%(s|d|f)') 


def scan_input(format_string, stream, max_size=float('+inf'), chunk_size=1024): 
    """Scan an input stream and retrieve formatted input.""" 

    chunk = '' 
    format_fields = format_string.split()[::-1] 
    while format_fields: 
     fields = FORMAT_FIELD_REGEX.findall(format_fields.pop()) 
     if not chunk: 
      chunk = _get_chunk(stream, chunk_size) 

     for field in fields: 
      field_regex = FORMATS_REGEXES[field] 
      match = field_regex.search(chunk) 
      length_before = len(chunk) 
      while match is None or match.end() >= len(chunk): 
       chunk += _get_chunk(stream, chunk_size) 
       if not chunk or length_before == len(chunk): 
        if match is None: 
         raise ValueError('Missing fields.') 
        break 
      text = match.group(1) 
      yield FORMATS_TYPES[field](text) 
      chunk = chunk[match.end():] 



def _get_chunk(stream, chunk_size): 
    try: 
     return stream.read(chunk_size) 
    except EOFError: 
     return '' 

Esempio di utilizzo:

>>> s = StringIO('1234 Hello World -13.48 -678 12.45') 
>>> for data in scan_input('%d %s %s %f %d %f', s): print repr(data) 
...                        
1234                       
'Hello' 
'World' 
-13.48 
-678 
12.45 

Probabilmente dovrete estendere questo, e testare in modo corretto ma dovrebbe darvi alcune idee .

1

Non esiste un equivalente diretto (per quanto ne so). Tuttavia, è possibile fare praticamente la stessa cosa con le espressioni regolari (vedere il modulo re).

Per esempio:

# matching first integer (space delimited) 
re.match(r'\b(\d+)\b',string) 

# matching first space delimited word 
re.match(r'\b(\w+)\b',string) 

# matching a word followed by an integer (space delimited) 
re.match(r'\b(\w+)\s+(\d+)\b',string) 

richiede un po 'più di lavoro rispetto al solito interfaccia dello scanner C-style, ma è anche molto flessibile e potente. Dovrai comunque elaborare I/O del flusso.