Domanda

Ho una lista di nomi di file di libreria che ho bisogno di filtrare contro espressioni regolari e quindi estrarre il numero di versione da quelli che corrispondono. Questo è il modo più ovvio per farlo:

libs = ['libIce.so.33', 'libIce.so.3.3.1', 'libIce.so.32', 'libIce.so.3.2.0']
versions = []
regex = re.compile('libIce.so\.([0-9]+\.[0-9]+\.[0-9]+)')
for l in libs:
    m = regex.match(l)
    if m:
        versions.append(m.group(1))

che produce il seguente elenco:

['3.3.1', '3.2.0']

Eppure sento che ciclo non è molto 'stile Python' e ritengo che dovrebbe essere possibile sostituire 'per' ciclo precedente con un po 'di smart one-liner. Suggerimenti?

È stato utile?

Soluzione

Come su un elenco di comprensione?

In [5]: versions = [m.group(1) for m in [regex.match(lib) for lib in libs] if m] 
In [6]: versions
Out[6]: ['3.3.1', '3.2.0']

Altri suggerimenti

Un altro one-liner solo per mostrare altri modi (ho anche pulito regexp un po '):

regex = re.compile(r'^libIce\.so\.([0-9]+\.[0-9]+\.[0-9]+)$')
sum(map(regex.findall, libs), [])

Ma nota, che la versione originale è più leggibile di tutti i suggerimenti. Vale la pena di cambiare?

Si potrebbe fare questo:

versions = [m.group(1) for m in [regex.match(l) for l in libs] if m]

Non credo che sia molto leggibile, anche se ...

Forse è più chiaro fatto in due fasi:

matches = [regex.match(l) for l in line]
versions = [m.group(1) for m in matches if m]

Non c'è niente che non è divinatorio sull'utilizzo di uno standard per ciclo. Tuttavia, è possibile utilizzare il mappa () funzione per generare una nuova lista sulla base dei risultati di una funzione di correre contro ogni elemento della lista.

non si ha realmente bisogno di perdere tempo con espressioni regolari per il vostro caso semplice

>>> libs = ['libIce.so.33', 'libIce.so.3.3.1', 'libIce.so.32', 'libIce.so.3.2.0']
>>> libs
['libIce.so.33', 'libIce.so.3.3.1', 'libIce.so.32', 'libIce.so.3.2.0']
>>> for i in libs:
...   print i.split("so.")
...
['libIce.', '33']
['libIce.', '3.3.1']
['libIce.', '32']
['libIce.', '3.2.0']
>>> for i in libs:
...   print i.split("so.")[-1]
...
33
3.3.1
32
3.2.0
>>>

fare un'ulteriore verifica per ottenere quelle con "punti".

Che ne dite di questo:

import re

def matches(regexp, list):
    'Regexp, [str] -> Iterable(Match or None)'
    return (regexp.match(s) for s in list)

libs = ['libIce.so.33', 'libIce.so.3.3.1', 'libIce.so.32', 'libIce.so.3.2.0']
regexp = re.compile('libIce.so\.([0-9]+\.[0-9]+\.[0-9]+)')
versions = [m.group(1) for m in matches(regexp, libs) if m is not None]

>>> print versions
['3.3.1', '3.2.0']

Un modo ho potuto pensare era quello di combinare 'mappare' e di lista.
La soluzione si presenta come di seguito:

import re  
libs = ['libIce.so.33', 'libIce.so.3.3.1', 'libIce.so.32', 'libIce.so.3.2.0']  
versions = []  

regex = re.compile('libIce.so\.([0-9]+\.[0-9]+\.[0-9]+)')  

def match(s):  
    m = regex.match(s)  
    if m:  
        return m.group(1)  

versions = [x for x in map(match,libs) if x]  

A partire Python 3.8, e l'introduzione di espressioni di assegnamento (PEP 572) (operatore :=), è possibile utilizzare una variabile locale all'interno di una lista di comprensione, al fine di evitare di chiamare due volte il risultato del corrispondente regex:

# libs = ['libIce.so.33', 'libIce.so.3.3.1', 'libIce.so.32', 'libIce.so.3.2.0']
# pattern = re.compile(r'libIce.so\.([0-9]+\.[0-9]+\.[0-9]+)')
[match.group(1) for lib in libs if (match := pattern.match(lib))]
# ['3.3.1', '3.2.0']

Questa:

  • Nomi valutazione dei pattern.match(lib) come match variabile (che è o None o un oggetto re.Match)
  • Utilizza questo match chiamato espressione in atto (sia None o un Match) per filtrare gli elementi non corrispondenti
  • E riutilizza match nel valore mappato estraendo il primo gruppo (match.group(1)).
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top