Vale la pena usare il re.compile di Python?


462

C'è qualche vantaggio nell'uso della compilazione per le espressioni regolari in Python?

h = re.compile('hello')
h.match('hello world')

vs

re.match('hello', 'hello world')

8
A parte il fatto che in 2.6 re.subnon prenderà argomento con le bandiere ...
new123456

58
Ho appena incontrato un caso in cui l'utilizzo ha re.compiledato un miglioramento 10-50x. La morale è che se hai molte regex (più di MAXCACHE = 100) e le usi molte volte ciascuna (e separate da più di MAXCACHE in mezzo, in modo che ognuna venga eliminata dalla cache: lo stesso molte volte e poi passare al successivo non conta), quindi sarebbe sicuramente utile compilarli. Altrimenti, non fa differenza.
ShreevatsaR,

8
Una piccola cosa da notare è che per le stringhe che non richiedono regex, il intest di sottostringa delle stringhe è MOLTO più veloce:>python -m timeit -s "import re" "re.match('hello', 'hello world')" 1000000 loops, best of 3: 1.41 usec per loop >python -m timeit "x = 'hello' in 'hello world'" 10000000 loops, best of 3: 0.0513 usec per loop
Gamrix

@ShreevatsaR Interessante! Puoi pubblicare una risposta con un esempio che mostra un miglioramento 10x-50x? La maggior parte delle risposte fornite qui mostra in realtà un miglioramento 3x in alcuni casi precisi e in altri casi quasi nessun miglioramento.
Basj

1
@Basj Fatto, ha inviato una risposta . Non mi sono preoccupato di scoprire a cosa stavo usando Python nel dicembre 2013, ma la prima cosa semplice che ho provato mostra lo stesso comportamento.
ShreevatsaR

Risposte:


436

Ho avuto molta esperienza con un regex compilato migliaia di volte rispetto alla compilazione al volo e non ho notato alcuna differenza percepibile. Ovviamente, questo è aneddotico, e certamente non è un grande argomento contro la compilazione, ma ho trovato la differenza trascurabile.

EDIT: Dopo una rapida occhiata al codice della libreria Python 2.5 attuale, vedo che Python compila regex e CACHES internamente ogni volta che le usi comunque (comprese le chiamate a re.match()), quindi stai davvero cambiando solo QUANDO viene compilato il regex e non dovresti ' risparmierai molto tempo - solo il tempo necessario per controllare la cache (una ricerca chiave su un dicttipo interno ).

Dal modulo re.py (i commenti sono miei):

def match(pattern, string, flags=0):
    return _compile(pattern, flags).match(string)

def _compile(*key):

    # Does cache check at top of function
    cachekey = (type(key[0]),) + key
    p = _cache.get(cachekey)
    if p is not None: return p

    # ...
    # Does actual compilation on cache miss
    # ...

    # Caches compiled regex
    if len(_cache) >= _MAXCACHE:
        _cache.clear()
    _cache[cachekey] = p
    return p

Continuo spesso a pre-compilare espressioni regolari, ma solo per associarle a un nome piacevole e riutilizzabile, non per alcun guadagno prestazionale previsto.


12
La tua conclusione non è coerente con la tua risposta. Se i regex vengono compilati e archiviati automaticamente, nella maggior parte dei casi non è necessario farlo manualmente.
jfs,

84
JF Sebastian, serve come segnale al programmatore che la regexp in questione verrà usata molto e non è pensata per essere un lancio.
Kaleissin,

40
Inoltre, direi che se non si desidera subire il hit di compilazione e cache in una parte critica delle prestazioni dell'applicazione, è meglio compilarli prima in una parte non critica dell'applicazione .
Eddie Parker,

20
Vedo il vantaggio principale di usare regex compilato se riutilizzi più volte lo stesso regex, riducendo così la possibilità di errori di battitura. Se la chiami una volta sola, la compilazione è più leggibile.
monkut

18
Quindi, la differenza principale sarà quando stai usando molte regex diverse (più di _MAXCACHE), alcune solo una volta e altre molte volte ... quindi è importante conservare le espressioni compilate per quelle che vengono utilizzate più non vengono scaricati dalla cache quando è pieno.
fortran,

133

Per me, il più grande vantaggio re.compileè la possibilità di separare la definizione della regex dal suo uso.

Anche un'espressione semplice come 0|[1-9][0-9]*(numero intero in base 10 senza zeri iniziali) può essere abbastanza complessa che preferiresti non dover riscriverla, controllare se hai fatto degli errori di battitura e successivamente ricontrollare se ci sono errori di battitura quando inizi il debug . Inoltre, è meglio usare un nome di variabile come num o num_b10 di 0|[1-9][0-9]*.

È certamente possibile memorizzare le stringhe e passarle a re.match; tuttavia, è meno leggibile:

num = "..."
# then, much later:
m = re.match(num, input)

Contro la compilazione:

num = re.compile("...")
# then, much later:
m = num.match(input)

Sebbene sia abbastanza vicino, l'ultima riga della seconda sembra più naturale e più semplice se usata ripetutamente.


5
Sono d'accordo con questa risposta; spesso usando re.compile si ottiene un codice più, non meno leggibile.
Carl Meyer,

1
A volte è vero il contrario, ad esempio se si definisce la regex in un posto e si usano i gruppi corrispondenti in un altro posto lontano.
Ken Williams,

1
@KenWilliams Non necessariamente, una regex ben denominata per uno scopo specifico dovrebbe essere chiara anche se usata lontano dalla definizione originale. Ad esempio us_phone_numbero social_security_numberecc.
Brian M. Sheldon,

2
@ BrianM.Sheldon nominare bene il regex non ti aiuta davvero a sapere cosa rappresentano i suoi vari gruppi di cattura.
Ken Williams,

68

FWIW:

$ python -m timeit -s "import re" "re.match('hello', 'hello world')"
100000 loops, best of 3: 3.82 usec per loop

$ python -m timeit -s "import re; h=re.compile('hello')" "h.match('hello world')"
1000000 loops, best of 3: 1.26 usec per loop

quindi, se stai usando molto lo stesso regex, potrebbe valerne la pena farlo re.compile(specialmente per regex più complessi).

Si applicano gli argomenti standard contro l'ottimizzazione prematura, ma non credo che tu perda davvero molta chiarezza / chiarezza usando re.compilese sospetti che i tuoi regexps possano diventare un collo di bottiglia nelle prestazioni.

Aggiornare:

Sotto Python 3.6 (ho il sospetto che i tempi di cui sopra siano stati eseguiti usando Python 2.x) e l'hardware 2018 (MacBook Pro), ora ottengo i seguenti tempi:

% python -m timeit -s "import re" "re.match('hello', 'hello world')"
1000000 loops, best of 3: 0.661 usec per loop

% python -m timeit -s "import re; h=re.compile('hello')" "h.match('hello world')"
1000000 loops, best of 3: 0.285 usec per loop

% python -m timeit -s "import re" "h=re.compile('hello'); h.match('hello world')"
1000000 loops, best of 3: 0.65 usec per loop

% python --version
Python 3.6.5 :: Anaconda, Inc.

Ho anche aggiunto un caso (notare le differenze tra virgolette tra le ultime due esecuzioni) che mostra che re.match(x, ...)è letteralmente [approssimativamente] equivalente a re.compile(x).match(...), cioè che non sembra accadere alcuna memorizzazione nella cache dietro la scena della rappresentazione compilata.


5
Principali problemi con la tua metodologia qui, poiché l'argomento di installazione NON è incluso nei tempi. Quindi, hai rimosso il tempo di compilazione dal secondo esempio e ne hai appena calcolato la media nel primo esempio. Ciò non significa che il primo esempio venga compilato ogni volta.
Trittico

1
Sì, sono d'accordo che questo non è un confronto equo dei due casi.
Kiv

7
Capisco cosa intendi, ma non è esattamente quello che succederebbe in una vera applicazione in cui regexp viene usato molte volte?
dF.

26
@Triptych, @Kiv: l'intero punto di compilare regexps separati dall'uso è minimizzare la compilazione; rimuoverlo dai tempi è esattamente ciò che dF avrebbe dovuto fare, perché rappresenta l'uso del mondo reale in modo più accurato. Il tempo di compilazione è particolarmente irrilevante con il modo in cui timeit.py fa i suoi tempi qui; esegue diverse esecuzioni e riporta solo la più breve, a quel punto la regexp compilata viene memorizzata nella cache. Il costo aggiuntivo che stai vedendo qui non è il costo di compilazione di regexp, ma il costo di cercarlo nella cache di regexp compilata (un dizionario).
jemfinch,

3
@Triptych Dovrebbe import reessere spostato fuori dalla configurazione? È tutto su dove vuoi misurare. Se avessi eseguito più volte uno script Python, sarebbe stato import recolpito dal tempo. Quando si confrontano le due, è importante separare le due linee per i tempi. Sì, come dici tu, quando avrai il tempo colpito. Il confronto mostra che o prendi il colpo di tempo una volta e ripeti il ​​colpo di tempo minore compilando o prendi il colpo ogni volta che si presume che la cache venga cancellata tra le chiamate, cosa che come è stato sottolineato potrebbe accadere. L'aggiunta di una tempistica di h=re.compile('hello')contribuirebbe a chiarire.
Tom Myddeltyn il

39

Ecco un semplice test case:

~$ for x in 1 10 100 1000 10000 100000 1000000; do python -m timeit -n $x -s 'import re' 're.match("[0-9]{3}-[0-9]{3}-[0-9]{4}", "123-123-1234")'; done
1 loops, best of 3: 3.1 usec per loop
10 loops, best of 3: 2.41 usec per loop
100 loops, best of 3: 2.24 usec per loop
1000 loops, best of 3: 2.21 usec per loop
10000 loops, best of 3: 2.23 usec per loop
100000 loops, best of 3: 2.24 usec per loop
1000000 loops, best of 3: 2.31 usec per loop

con re.compile:

~$ for x in 1 10 100 1000 10000 100000 1000000; do python -m timeit -n $x -s 'import re' 'r = re.compile("[0-9]{3}-[0-9]{3}-[0-9]{4}")' 'r.match("123-123-1234")'; done
1 loops, best of 3: 1.91 usec per loop
10 loops, best of 3: 0.691 usec per loop
100 loops, best of 3: 0.701 usec per loop
1000 loops, best of 3: 0.684 usec per loop
10000 loops, best of 3: 0.682 usec per loop
100000 loops, best of 3: 0.694 usec per loop
1000000 loops, best of 3: 0.702 usec per loop

Quindi, sembra che la compilazione sia più veloce con questo semplice caso, anche se abbini una sola volta .


2
Quale versione di Python è questa?
Kyle Strand,

2
non importa, il punto è provare il benchmark nell'ambiente in cui eseguirai il codice
David King,

1
Per me le prestazioni sono quasi identiche per 1000 loop o più. La versione compilata è più veloce per 1-100 loop. (Su entrambi i pitoni 2.7 e 3.4).
Zitrax,

2
Nel mio setup di Python 2.7.3 non c'è praticamente alcuna differenza. A volte la compilazione è più veloce, a volte è più lenta. La differenza è sempre <5%, quindi conto la differenza come misura dell'incertezza, poiché il dispositivo ha solo una CPU.
Dakkaron,

1
In Python 3.4.3 visto in due esecuzioni separate: l'utilizzo della compilazione era persino più lento di non compilato.
Zelphir Kaltstahl,

17

Ho appena provato questo. Per il semplice caso di analizzare un numero da una stringa e sommarlo, l'uso di un oggetto di espressione regolare compilato è circa due volte più veloce rispetto all'utilizzo dei remetodi.

Come altri hanno sottolineato, i remetodi (incluso re.compile) cercano la stringa di espressioni regolari in una cache di espressioni precedentemente compilate. Pertanto, nel caso normale, il costo aggiuntivo dell'uso dei remetodi è semplicemente il costo della ricerca nella cache.

Tuttavia, l'esame del codice mostra che la cache è limitata a 100 espressioni. Questo pone la domanda, quanto è doloroso traboccare la cache? Il codice contiene un'interfaccia interna al normale compilatore espressione re.sre_compile.compile. Se lo chiamiamo, ignoriamo la cache. Risulta essere circa due ordini di grandezza più lenti per un'espressione regolare di base, come r'\w+\s+([0-9_]+)\s+\w*'.

Ecco il mio test:

#!/usr/bin/env python
import re
import time

def timed(func):
    def wrapper(*args):
        t = time.time()
        result = func(*args)
        t = time.time() - t
        print '%s took %.3f seconds.' % (func.func_name, t)
        return result
    return wrapper

regularExpression = r'\w+\s+([0-9_]+)\s+\w*'
testString = "average    2 never"

@timed
def noncompiled():
    a = 0
    for x in xrange(1000000):
        m = re.match(regularExpression, testString)
        a += int(m.group(1))
    return a

@timed
def compiled():
    a = 0
    rgx = re.compile(regularExpression)
    for x in xrange(1000000):
        m = rgx.match(testString)
        a += int(m.group(1))
    return a

@timed
def reallyCompiled():
    a = 0
    rgx = re.sre_compile.compile(regularExpression)
    for x in xrange(1000000):
        m = rgx.match(testString)
        a += int(m.group(1))
    return a


@timed
def compiledInLoop():
    a = 0
    for x in xrange(1000000):
        rgx = re.compile(regularExpression)
        m = rgx.match(testString)
        a += int(m.group(1))
    return a

@timed
def reallyCompiledInLoop():
    a = 0
    for x in xrange(10000):
        rgx = re.sre_compile.compile(regularExpression)
        m = rgx.match(testString)
        a += int(m.group(1))
    return a

r1 = noncompiled()
r2 = compiled()
r3 = reallyCompiled()
r4 = compiledInLoop()
r5 = reallyCompiledInLoop()
print "r1 = ", r1
print "r2 = ", r2
print "r3 = ", r3
print "r4 = ", r4
print "r5 = ", r5
</pre>
And here is the output on my machine:
<pre>
$ regexTest.py 
noncompiled took 4.555 seconds.
compiled took 2.323 seconds.
reallyCompiled took 2.325 seconds.
compiledInLoop took 4.620 seconds.
reallyCompiledInLoop took 4.074 seconds.
r1 =  2000000
r2 =  2000000
r3 =  2000000
r4 =  2000000
r5 =  20000

I metodi "reallyCompiled" utilizzano l'interfaccia interna, che ignora la cache. Nota che quello che compila su ogni iterazione di loop viene ripetuto solo 10.000 volte, non un milione.


Sono d'accordo con te sul fatto che le regex compilate sono molto più veloci di quelle non compilate. Ho eseguito oltre 10.000 frasi e ho creato un ciclo in esse per ripetere le regex quando le regex non sono state compilate e sono state calcolate ogni volta che la previsione di una corsa completa era di 8 ore, dopo aver creato un dizionario in base all'indice con schemi regex compilati che eseguo il tutto per 2 minuti. Non riesco a capire le risposte sopra ...
Eli Borodach il

12

Concordo con Honest Abe sul fatto che gli match(...)esempi riportati sono diversi. Non sono confronti uno a uno e quindi i risultati sono diversi. Per semplificare la mia risposta, uso A, B, C, D per quelle funzioni in questione. Oh sì, abbiamo a che fare con 4 funzioni re.pyinvece di 3.

Esecuzione di questo codice:

h = re.compile('hello')                   # (A)
h.match('hello world')                    # (B)

equivale a eseguire questo codice:

re.match('hello', 'hello world')          # (C)

Perché, quando guardato nella fonte re.py, (A + B) significa:

h = re._compile('hello')                  # (D)
h.match('hello world')

e (C) è in realtà:

re._compile('hello').match('hello world')

Quindi, (C) non è la stessa di (B). In effetti, (C) chiama (B) dopo aver chiamato (D) che è anche chiamato da (A). In altre parole, (C) = (A) + (B). Pertanto, il confronto (A + B) all'interno di un loop ha lo stesso risultato di (C) all'interno di un loop.

George lo ha regexTest.pydimostrato per noi.

noncompiled took 4.555 seconds.           # (C) in a loop
compiledInLoop took 4.620 seconds.        # (A + B) in a loop
compiled took 2.323 seconds.              # (A) once + (B) in a loop

L'interesse di tutti è, come ottenere il risultato di 2.323 secondi. Per essere sicuri di compile(...)essere chiamato solo una volta, abbiamo bisogno di memorizzare l'oggetto regex compilato in memoria. Se stiamo usando una classe, potremmo memorizzare l'oggetto e riutilizzarlo ogni volta che viene chiamata la nostra funzione.

class Foo:
    regex = re.compile('hello')
    def my_function(text)
        return regex.match(text)

Se non stiamo usando la classe (che è la mia richiesta oggi), allora non ho commenti. Sto ancora imparando a usare la variabile globale in Python e so che la variabile globale è una cosa negativa.

Un altro punto, credo che l'utilizzo (A) + (B)dell'approccio abbia un vantaggio. Ecco alcuni fatti che ho osservato (per favore correggimi se sbaglio):

  1. Chiama A una volta, eseguirà una ricerca nel _cacheseguito da uno sre_compile.compile()per creare un oggetto regex. Chiama A due volte, eseguirà due ricerche e una compilazione (poiché l'oggetto regex è memorizzato nella cache).

  2. Se _cacheviene svuotato nel mezzo, l'oggetto regex viene rilasciato dalla memoria e Python deve essere nuovamente compilato. (qualcuno suggerisce che Python non si ricompilerà.)

  3. Se manteniamo l'oggetto regex usando (A), l'oggetto regex entrerà comunque in _cache e verrà scaricato in qualche modo. Ma il nostro codice mantiene un riferimento su di esso e l'oggetto regex non verrà rilasciato dalla memoria. Quelli, Python non ha bisogno di compilare di nuovo.

  4. Le differenze di 2 secondi nel test di George compilatoInLoop vs compilated è principalmente il tempo necessario per compilare la chiave e cercare _cache. Non significa il tempo di compilazione di regex.

  5. Il test davvero completo di George mostra cosa succede se rielabora davvero la compilazione ogni volta: sarà 100 volte più lento (ha ridotto il loop da 1.000.000 a 10.000).

Ecco gli unici casi in cui (A + B) è migliore di (C):

  1. Se possiamo memorizzare nella cache un riferimento all'oggetto regex all'interno di una classe.
  2. Se abbiamo bisogno di chiamare ripetutamente (B) (all'interno di un loop o più volte), dobbiamo memorizzare nella cache il riferimento all'oggetto regex all'esterno del loop.

Caso che (C) è abbastanza buono:

  1. Non è possibile memorizzare nella cache un riferimento.
  2. Lo usiamo solo una volta ogni tanto.
  3. Nel complesso, non abbiamo troppi regex (supponiamo che quello compilato non venga mai scaricato)

Solo un riassunto, ecco l'ABC:

h = re.compile('hello')                   # (A)
h.match('hello world')                    # (B)
re.match('hello', 'hello world')          # (C)

Grazie per aver letto.


8

Principalmente, c'è poca differenza se usi re.compile o no. Internamente, tutte le funzioni sono implementate in termini di una fase di compilazione:

def match(pattern, string, flags=0):
    return _compile(pattern, flags).match(string)

def fullmatch(pattern, string, flags=0):
    return _compile(pattern, flags).fullmatch(string)

def search(pattern, string, flags=0):
    return _compile(pattern, flags).search(string)

def sub(pattern, repl, string, count=0, flags=0):
    return _compile(pattern, flags).sub(repl, string, count)

def subn(pattern, repl, string, count=0, flags=0):
    return _compile(pattern, flags).subn(repl, string, count)

def split(pattern, string, maxsplit=0, flags=0):
    return _compile(pattern, flags).split(string, maxsplit)

def findall(pattern, string, flags=0):
    return _compile(pattern, flags).findall(string)

def finditer(pattern, string, flags=0):
    return _compile(pattern, flags).finditer(string)

Inoltre, re.compile () ignora la logica di indirizzamento e memorizzazione nella cache aggiuntiva:

_cache = {}

_pattern_type = type(sre_compile.compile("", 0))

_MAXCACHE = 512
def _compile(pattern, flags):
    # internal: compile pattern
    try:
        p, loc = _cache[type(pattern), pattern, flags]
        if loc is None or loc == _locale.setlocale(_locale.LC_CTYPE):
            return p
    except KeyError:
        pass
    if isinstance(pattern, _pattern_type):
        if flags:
            raise ValueError(
                "cannot process flags argument with a compiled pattern")
        return pattern
    if not sre_compile.isstring(pattern):
        raise TypeError("first argument must be string or compiled pattern")
    p = sre_compile.compile(pattern, flags)
    if not (flags & DEBUG):
        if len(_cache) >= _MAXCACHE:
            _cache.clear()
        if p.flags & LOCALE:
            if not _locale:
                return p
            loc = _locale.setlocale(_locale.LC_CTYPE)
        else:
            loc = None
        _cache[type(pattern), pattern, flags] = p, loc
    return p

Oltre al vantaggio ridotto della velocità derivante dall'utilizzo di re.compile , le persone apprezzano anche la leggibilità derivante dalla denominazione di specifiche di pattern potenzialmente complesse e dalla loro separazione dalla logica aziendale in cui sono applicate:

#### Patterns ############################################################
number_pattern = re.compile(r'\d+(\.\d*)?')    # Integer or decimal number
assign_pattern = re.compile(r':=')             # Assignment operator
identifier_pattern = re.compile(r'[A-Za-z]+')  # Identifiers
whitespace_pattern = re.compile(r'[\t ]+')     # Spaces and tabs

#### Applications ########################################################

if whitespace_pattern.match(s): business_logic_rule_1()
if assign_pattern.match(s): business_logic_rule_2()

Nota, un altro rispondente ha erroneamente ritenuto che i file pyc memorizzassero direttamente i modelli compilati; tuttavia, in realtà vengono ricostruiti ogni volta che viene caricato il PYC:

>>> from dis import dis
>>> with open('tmp.pyc', 'rb') as f:
        f.read(8)
        dis(marshal.load(f))

  1           0 LOAD_CONST               0 (-1)
              3 LOAD_CONST               1 (None)
              6 IMPORT_NAME              0 (re)
              9 STORE_NAME               0 (re)

  3          12 LOAD_NAME                0 (re)
             15 LOAD_ATTR                1 (compile)
             18 LOAD_CONST               2 ('[aeiou]{2,5}')
             21 CALL_FUNCTION            1
             24 STORE_NAME               2 (lc_vowels)
             27 LOAD_CONST               1 (None)
             30 RETURN_VALUE

Lo smontaggio di cui sopra proviene dal file PYC per un tmp.pycontenente:

import re
lc_vowels = re.compile(r'[aeiou]{2,5}')

1
è "in def search(pattern, string, flags=0):"un refuso?
phuclv,

1
Si noti che se patternè già un modello compilato, l'overhead della memorizzazione nella cache diventa significativo: l'hashing a SRE_Patternè costoso e il modello non viene mai scritto nella cache, quindi la ricerca fallisce ogni volta con a KeyError.
Eric Duminil,

5

In generale, trovo che sia più facile usare i flag (almeno più facile ricordare come), come re.Iquando si compilano i pattern piuttosto che usare i flag in linea.

>>> foo_pat = re.compile('foo',re.I)
>>> foo_pat.findall('some string FoO bar')
['FoO']

vs

>>> re.findall('(?i)foo','some string FoO bar')
['FoO']

In ogni caso, potresti usare le bandiere come terzo argomento re.findall.
aderchox

5

Utilizzando gli esempi forniti:

h = re.compile('hello')
h.match('hello world')

Il metodo di corrispondenza nell'esempio precedente non è uguale a quello utilizzato di seguito:

re.match('hello', 'hello world')

re.compile () restituisce un oggetto espressione regolare , il che significa che hè un oggetto regex.

L'oggetto regex ha il suo metodo match con i parametri pos ed endpos opzionali :

regex.match(string[, pos[, endpos]])

pos

Il secondo parametro opzionale pos fornisce un indice nella stringa da cui deve iniziare la ricerca; il suo valore predefinito è 0. Questo non è del tutto equivalente al taglio della stringa; il '^'carattere del motivo corrisponde all'inizio reale della stringa e nelle posizioni subito dopo una nuova riga, ma non necessariamente nell'indice in cui deve iniziare la ricerca.

endpos

Il parametro opzionale endpos limita fino a che punto verrà cercata la stringa; sarà come se la stringa fosse lunga caratteri endpos , quindi solo i caratteri da pos a endpos - 1verranno cercati una corrispondenza. Se endpos è inferiore a pos , non verrà trovata alcuna corrispondenza; altrimenti, se rx è un oggetto espressione regolare compilato, rx.search(string, 0, 50)è equivalente a rx.search(string[:50], 0).

Anche i metodi di ricerca , findall e finditer dell'oggetto regex supportano questi parametri.

re.match(pattern, string, flags=0)non li supporta come puoi vedere,
né le sue controparti di ricerca , findall e finditer .

Un oggetto match ha attributi che completano questi parametri:

match.pos

Il valore di pos che è stato passato al metodo search () o match () di un oggetto regex. Questo è l'indice nella stringa in cui il motore RE ha iniziato a cercare una corrispondenza.

match.endpos

Il valore di endpos che è stato passato al metodo search () o match () di un oggetto regex. Questo è l'indice nella stringa oltre la quale il motore RE non andrà.


Un oggetto regex ha due attributi unici, forse utili,:

regex.groups

Il numero di gruppi di acquisizione nel modello.

regex.groupindex

Un dizionario che mappa i nomi di gruppi simbolici definiti da (? P) ai numeri di gruppo. Il dizionario è vuoto se non sono stati utilizzati gruppi simbolici nel modello.


E infine, un oggetto match ha questo attributo:

match.re

L'oggetto espressione regolare il cui metodo match () o search () ha prodotto questa istanza di corrispondenza.


4

A parte la differenza di prestazioni, l'uso di re.compile e l'uso dell'oggetto compilato dell'espressione regolare per fare corrispondenze (qualunque operazione relativa all'espressione regolare) rende la semantica più chiara al runtime di Python.

Ho avuto una dolorosa esperienza di debug di alcuni semplici codici:

compare = lambda s, p: re.match(p, s)

e più tardi userei compare in

[x for x in data if compare(patternPhrases, x[columnIndex])]

dove patternPhrasessi suppone che sia una variabile contenente una stringa di espressione regolare, x[columnIndex]è una variabile contenente una stringa.

Ho avuto problemi che patternPhrasesnon corrispondevano a una stringa prevista!

Ma se ho usato il modulo re.compile:

compare = lambda s, p: p.match(s)

poi dentro

[x for x in data if compare(patternPhrases, x[columnIndex])]

Python sarebbe lamentato che "stringa non ha attributo della partita", come per la mappatura argomento posizionale in compare, x[columnIndex]viene utilizzato come espressione regolare !, quando ho effettivamente dire

compare = lambda p, s: p.match(s)

Nel mio caso, l'uso di re.compile è più esplicito dello scopo dell'espressione regolare, quando il suo valore è nascosto ad occhi nudi, quindi potrei ottenere più aiuto dal controllo del runtime di Python.

Quindi la morale della mia lezione è che quando l'espressione regolare non è solo una stringa letterale, allora dovrei usare re.compile per lasciare che Python mi aiuti ad affermare la mia ipotesi.


4

C'è un vantaggio aggiuntivo dell'utilizzo di re.compile (), sotto forma di aggiunta di commenti ai miei schemi regex usando re.VERBOSE

pattern = '''
hello[ ]world    # Some info on my pattern logic. [ ] to recognize space
'''

re.search(pattern, 'hello world', re.VERBOSE)

Sebbene ciò non influisca sulla velocità di esecuzione del codice, mi piace farlo in questo modo poiché fa parte della mia abitudine di commento. Non mi piace passare il tempo cercando di ricordare la logica che è andata dietro il mio codice 2 mesi dopo quando voglio apportare modifiche.


1
Ho modificato la tua risposta. Penso che menzionare re.VERBOSEsia utile e aggiunga qualcosa che le altre risposte sembrano aver tralasciato. Tuttavia, portando la tua risposta con "Sto pubblicando qui perché non posso ancora commentare" è sicuro di averlo eliminato. Si prega di non utilizzare la casella delle risposte per altro che le risposte. Sei solo una o due buone risposte di poter commentare ovunque (50 ripetizioni), quindi per favore sii paziente. Inserire commenti nelle caselle di risposta quando sai che non dovresti farti arrivare più velocemente. Riceverai i voti negativi e le risposte cancellate.
skrrgwasme,

4

Secondo la documentazione di Python :

La sequenza

prog = re.compile(pattern)
result = prog.match(string)

è equivalente a

result = re.match(pattern, string)

ma l'uso re.compile()e il salvataggio dell'oggetto risultante dell'espressione regolare per il riutilizzo è più efficiente quando l'espressione verrà utilizzata più volte in un singolo programma.

Quindi la mia conclusione è che, se hai intenzione di abbinare lo stesso schema per molti testi diversi, è meglio precompilarlo.


3

È interessante notare che la compilazione si rivela più efficiente per me (Python 2.5.2 su Win XP):

import re
import time

rgx = re.compile('(\w+)\s+[0-9_]?\s+\w*')
str = "average    2 never"
a = 0

t = time.time()

for i in xrange(1000000):
    if re.match('(\w+)\s+[0-9_]?\s+\w*', str):
    #~ if rgx.match(str):
        a += 1

print time.time() - t

Eseguendo il codice sopra una volta com'è, e una volta con le due ifrighe commentate al contrario, la regex compilata è due volte più veloce


2
Stesso problema del confronto delle prestazioni di dF. Non è proprio corretto a meno che non si includa il costo delle prestazioni dell'istruzione di compilazione stessa.
Carl Meyer,

6
Carl, non sono d'accordo. La compilazione viene eseguita una sola volta, mentre il loop corrispondente viene eseguito un milione di volte
Eli Bendersky,

@eliben: sono d'accordo con Carl Meyer. La compilazione si svolge in entrambi i casi. Triptych menziona che è coinvolta la memorizzazione nella cache, quindi in un caso ottimale (rimane nella cache) entrambi gli approcci sono O (n + 1), sebbene la parte +1 sia nascosta quando non si usa re.compile in modo esplicito.
paprika,

1
Non scrivere il tuo codice di benchmarking. Impara a usare timeit.py, incluso nella distribuzione standard.
jemfinch,

Quanto tempo stai ricreando la stringa del modello nel ciclo for. Questo sovraccarico non può essere banale.
IceArdor,

3

Ho eseguito questo test prima di imbattermi nella discussione qui. Tuttavia, dopo averlo eseguito, ho pensato di pubblicare almeno i miei risultati.

Ho rubato e bastardato l'esempio di "Mastering Regular Expressions" di Jeff Friedl. Questo è su un macbook con OSX 10.6 (2Ghz Intel Core 2 Duo, 4 GB di RAM). La versione di Python è 2.6.1.

Esegui 1 - utilizzando re.compile

import re 
import time 
import fpformat
Regex1 = re.compile('^(a|b|c|d|e|f|g)+$') 
Regex2 = re.compile('^[a-g]+$')
TimesToDo = 1000
TestString = "" 
for i in range(1000):
    TestString += "abababdedfg"
StartTime = time.time() 
for i in range(TimesToDo):
    Regex1.search(TestString) 
Seconds = time.time() - StartTime 
print "Alternation takes " + fpformat.fix(Seconds,3) + " seconds"

StartTime = time.time() 
for i in range(TimesToDo):
    Regex2.search(TestString) 
Seconds = time.time() - StartTime 
print "Character Class takes " + fpformat.fix(Seconds,3) + " seconds"

Alternation takes 2.299 seconds
Character Class takes 0.107 seconds

Esegui 2: non utilizza re.compile

import re 
import time 
import fpformat

TimesToDo = 1000
TestString = "" 
for i in range(1000):
    TestString += "abababdedfg"
StartTime = time.time() 
for i in range(TimesToDo):
    re.search('^(a|b|c|d|e|f|g)+$',TestString) 
Seconds = time.time() - StartTime 
print "Alternation takes " + fpformat.fix(Seconds,3) + " seconds"

StartTime = time.time() 
for i in range(TimesToDo):
    re.search('^[a-g]+$',TestString) 
Seconds = time.time() - StartTime 
print "Character Class takes " + fpformat.fix(Seconds,3) + " seconds"

Alternation takes 2.508 seconds
Character Class takes 0.109 seconds

3

Questa risposta potrebbe arrivare in ritardo ma è una scoperta interessante. L'uso della compilazione può davvero farti risparmiare tempo se hai intenzione di utilizzare la regex più volte (questo è anche menzionato nei documenti). Di seguito puoi vedere che l'uso di una regex compilata è il più veloce quando il metodo di corrispondenza viene chiamato direttamente su di esso. passare un regex compilato a re.match lo rende ancora più lento e passare re.match con la stringa patter è da qualche parte nel mezzo.

>>> ipr = r'\D+((([0-2][0-5]?[0-5]?)\.){3}([0-2][0-5]?[0-5]?))\D+'
>>> average(*timeit.repeat("re.match(ipr, 'abcd100.10.255.255 ')", globals={'ipr': ipr, 're': re}))
1.5077415757028423
>>> ipr = re.compile(ipr)
>>> average(*timeit.repeat("re.match(ipr, 'abcd100.10.255.255 ')", globals={'ipr': ipr, 're': re}))
1.8324008992184038
>>> average(*timeit.repeat("ipr.match('abcd100.10.255.255 ')", globals={'ipr': ipr, 're': re}))
0.9187896518778871

3

Oltre alle prestazioni.

L'utilizzo compilemi aiuta a distinguere i concetti di
1. module (re) ,
2. regex object
3. match object
Quando ho iniziato a studiare regex

#regex object
regex_object = re.compile(r'[a-zA-Z]+')
#match object
match_object = regex_object.search('1.Hello')
#matching content
match_object.group()
output:
Out[60]: 'Hello'
V.S.
re.search(r'[a-zA-Z]+','1.Hello').group()
Out[61]: 'Hello'

Come complemento, ho creato un completo cheatsheet di moduli reper il tuo riferimento.

regex = {
'brackets':{'single_character': ['[]', '.', {'negate':'^'}],
            'capturing_group' : ['()','(?:)', '(?!)' '|', '\\', 'backreferences and named group'],
            'repetition'      : ['{}', '*?', '+?', '??', 'greedy v.s. lazy ?']},
'lookaround' :{'lookahead'  : ['(?=...)', '(?!...)'],
            'lookbehind' : ['(?<=...)','(?<!...)'],
            'caputuring' : ['(?P<name>...)', '(?P=name)', '(?:)'],},
'escapes':{'anchor'          : ['^', '\b', '$'],
          'non_printable'   : ['\n', '\t', '\r', '\f', '\v'],
          'shorthand'       : ['\d', '\w', '\s']},
'methods': {['search', 'match', 'findall', 'finditer'],
              ['split', 'sub']},
'match_object': ['group','groups', 'groupdict','start', 'end', 'span',]
}

2

Rispetto davvero tutte le risposte di cui sopra. Dalla mia opinione Sì! Sicuramente vale la pena usare re.compile invece di compilare regex, ancora e ancora, ogni volta.

L'uso di re.compile rende il codice più dinamico, come è possibile chiamare il regex già compilato, invece di compilare nuovamente e nuovamente. Questa cosa ti avvantaggia nei casi:

  1. Sforzi del processore
  2. Complessità temporale.
  3. Rende regex universale (può essere utilizzato in findall, search, match)
  4. E rende il tuo programma bello.

Esempio :

  example_string = "The room number of her room is 26A7B."
  find_alpha_numeric_string = re.compile(r"\b\w+\b")

Utilizzo in Findall

 find_alpha_numeric_string.findall(example_string)

Usando nella ricerca

  find_alpha_numeric_string.search(example_string)

Allo stesso modo puoi usarlo per: Abbina e sostituisci


1

Questa è una buona domanda Spesso vedi persone che usano re.compile senza motivo. Riduce la leggibilità. Ma sicuramente ci sono molte volte quando è richiesta la pre-compilazione dell'espressione. Come quando lo usi più volte in un ciclo o in qualche modo.

È come tutto ciò che riguarda la programmazione (tutto nella vita in realtà). Applica il buon senso.


Per quanto ne so dal mio breve passaggio, Python in a Nutshell non menziona l'uso senza re.compile (), il che mi ha incuriosito.
Mat,

L'oggetto regex aggiunge un altro oggetto al contesto. Come ho detto, esistono molte situazioni in cui re.compile () ha il suo posto. L'esempio fornito dall'OP non è uno di questi.
PEZ

1

(mesi dopo) è facile aggiungere la propria cache a re.match o qualsiasi altra cosa -

""" Re.py: Re.match = re.match + cache  
    efficiency: re.py does this already (but what's _MAXCACHE ?)
    readability, inline / separate: matter of taste
"""

import re

cache = {}
_re_type = type( re.compile( "" ))

def match( pattern, str, *opt ):
    """ Re.match = re.match + cache re.compile( pattern ) 
    """
    if type(pattern) == _re_type:
        cpat = pattern
    elif pattern in cache:
        cpat = cache[pattern]
    else:
        cpat = cache[pattern] = re.compile( pattern, *opt )
    return cpat.match( str )

# def search ...

Un wibni, non sarebbe bello se: cachehint (size =), cacheinfo () -> size, hits, nclear ...


1

Ho avuto molta esperienza con un regex compilato migliaia di volte rispetto alla compilazione al volo e non ho notato alcuna differenza percepibile

I voti sulla risposta accettata portano all'assunto che ciò che dice @Triptych è vero per tutti i casi. Questo non è necessariamente vero. Una grande differenza è quando devi decidere se accettare una stringa regex o un oggetto regex compilato come parametro per una funzione:

>>> timeit.timeit(setup="""
... import re
... f=lambda x, y: x.match(y)       # accepts compiled regex as parameter
... h=re.compile('hello')
... """, stmt="f(h, 'hello world')")
0.32881879806518555
>>> timeit.timeit(setup="""
... import re
... f=lambda x, y: re.compile(x).match(y)   # compiles when called
... """, stmt="f('hello', 'hello world')")
0.809190034866333

È sempre meglio compilare i tuoi regex nel caso in cui sia necessario riutilizzarli.

Nota l'esempio nel timeit sopra simula la creazione di un oggetto regex compilato una volta al momento dell'importazione rispetto a "al volo" quando richiesto per una corrispondenza.


1

Come risposta alternativa, visto che non è mai stato menzionato prima, andrò avanti e citerò i documenti di Python 3 :

Dovresti usare queste funzioni a livello di modulo, o dovresti ottenere lo schema e chiamare tu stesso i suoi metodi? Se accedi a un regex all'interno di un loop, la pre-compilazione salverà alcune chiamate di funzione. Al di fuori dei loop, non c'è molta differenza grazie alla cache interna.


1

Ecco un esempio in cui l'utilizzo re.compileè oltre 50 volte più veloce, come richiesto .

Il punto è lo stesso di quello che ho fatto nel commento sopra, vale a dire, l'utilizzo re.compilepuò essere un vantaggio significativo quando il tuo utilizzo è tale da non trarre molto beneficio dalla cache della compilazione. Ciò accade almeno in un caso particolare (che ho riscontrato nella pratica), vale a dire quando sono vere tutte le seguenti condizioni:

  • Hai molti pattern regex (più di re._MAXCACHE, il cui valore predefinito è attualmente 512) e
  • usi queste regex molte volte, e
  • gli usi consecutivi dello stesso modello sono separati da più di re._MAXCACHEaltre regex tra loro, in modo che ognuno venga svuotato dalla cache tra usi consecutivi.
import re
import time

def setup(N=1000):
    # Patterns 'a.*a', 'a.*b', ..., 'z.*z'
    patterns = [chr(i) + '.*' + chr(j)
                    for i in range(ord('a'), ord('z') + 1)
                    for j in range(ord('a'), ord('z') + 1)]
    # If this assertion below fails, just add more (distinct) patterns.
    # assert(re._MAXCACHE < len(patterns))
    # N strings. Increase N for larger effect.
    strings = ['abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz'] * N
    return (patterns, strings)

def without_compile():
    print('Without re.compile:')
    patterns, strings = setup()
    print('searching')
    count = 0
    for s in strings:
        for pat in patterns:
            count += bool(re.search(pat, s))
    return count

def without_compile_cache_friendly():
    print('Without re.compile, cache-friendly order:')
    patterns, strings = setup()
    print('searching')
    count = 0
    for pat in patterns:
        for s in strings:
            count += bool(re.search(pat, s))
    return count

def with_compile():
    print('With re.compile:')
    patterns, strings = setup()
    print('compiling')
    compiled = [re.compile(pattern) for pattern in patterns]
    print('searching')
    count = 0
    for s in strings:
        for regex in compiled:
            count += bool(regex.search(s))
    return count

start = time.time()
print(with_compile())
d1 = time.time() - start
print(f'-- That took {d1:.2f} seconds.\n')

start = time.time()
print(without_compile_cache_friendly())
d2 = time.time() - start
print(f'-- That took {d2:.2f} seconds.\n')

start = time.time()
print(without_compile())
d3 = time.time() - start
print(f'-- That took {d3:.2f} seconds.\n')

print(f'Ratio: {d3/d1:.2f}')

Esempio di output che ottengo sul mio laptop (Python 3.7.7):

With re.compile:
compiling
searching
676000
-- That took 0.33 seconds.

Without re.compile, cache-friendly order:
searching
676000
-- That took 0.67 seconds.

Without re.compile:
searching
676000
-- That took 23.54 seconds.

Ratio: 70.89

Non mi sono preoccupato timeitperché la differenza è così netta, ma ogni volta ricevo numeri qualitativamente simili. Nota che anche senza re.compile, usare lo stesso regex più volte e passare al successivo non è stato così male (solo circa 2 volte più lento di con re.compile), ma nell'altro ordine (passando attraverso molte regex), è significativamente peggio , come previsto. Inoltre, aumentando la dimensione della cache lavora troppo: semplicemente impostando re._MAXCACHE = len(patterns)in setup()precedenza (ovviamente io non consiglio di fare queste cose in produzione come nomi con sottolineature sono convenzionalmente “privato”) lascia cadere il ~ 23 secondi giù a ~ 0,7 secondi, che anche corrisponde alla nostra comprensione.


PS: se uso solo 3 pattern regex in tutto il mio codice, ognuno di essi usato (senza alcun ordine specifico) centinaia di volte, la cache di regex manterrà automaticamente il regex precompilato, giusto?
Basj

@Basj Penso che potresti semplicemente provarlo e vedere :) Ma la risposta, ne sono abbastanza sicuro, è sì: l'unico costo aggiuntivo in quel caso AFAICT è proprio quello di cercare semplicemente lo schema nella cache . Nota anche che la cache è globale (a livello di modulo), quindi in linea di principio potresti avere una libreria di dipendenze che fa ricerche tra regex tra le tue, quindi è difficile essere pienamente sicuri che il tuo programma usi sempre solo 3 (o qualunque numero di) regex modelli, ma sarebbe piuttosto strano essere altrimenti :)
ShreevatsaR

0

Le espressioni regolari vengono compilate prima di essere utilizzate quando si utilizza la seconda versione. Se hai intenzione di eseguirlo molte volte è sicuramente meglio compilarlo prima. Se non si compila ogni volta che si abbina per una volta va bene.


0

Preferibilità di leggibilità / carico cognitivo

Per me, il vantaggio principale è che ho solo bisogno di ricordare e leggere una forma della complicata sintassi dell'API regex: il <compiled_pattern>.method(xxx)modulo piuttosto che quello e il re.func(<pattern>, xxx)modulo.

Il re.compile(<pattern>)è un po 'di testo standard in più, è vero.

Ma per quanto riguarda regex, è improbabile che questa fase di compilazione aggiuntiva sia una grande causa di carico cognitivo. E in effetti, su schemi complicati, potresti persino ottenere chiarezza separando la dichiarazione da qualunque metodo regex che invochi su di essa.

Tendo innanzitutto a mettere a punto schemi complicati in un sito Web come Regex101, o anche in uno script di test minimo separato, quindi inserirli nel mio codice, quindi separare la dichiarazione dal suo utilizzo si adatta anche al mio flusso di lavoro.


-1

Vorrei motivare che la pre-compilazione è sia concettualmente che "letteralmente" (come nella "programmazione alfabetica") vantaggiosa. dai un'occhiata a questo frammento di codice:

from re import compile as _Re

class TYPO:

  def text_has_foobar( self, text ):
    return self._text_has_foobar_re_search( text ) is not None
  _text_has_foobar_re_search = _Re( r"""(?i)foobar""" ).search

TYPO = TYPO()

nella tua applicazione, dovresti scrivere:

from TYPO import TYPO
print( TYPO.text_has_foobar( 'FOObar ) )

questo è più semplice in termini di funzionalità che può ottenere. poiché questo esempio è così breve, ho combinato il modo di ottenere _text_has_foobar_re_searchtutto in una riga. lo svantaggio di questo codice è che occupa un po 'di memoria per qualunque sia la durata TYPOdell'oggetto libreria; il vantaggio è che quando si esegue una ricerca foobar, si evitano due chiamate di funzione e due ricerche di dizionario di classe. quanti regex sono memorizzati nella cache ree l'overhead di quella cache non è rilevante qui.

confronta questo con lo stile più consueto, di seguito:

import re

class Typo:

  def text_has_foobar( self, text ):
    return re.compile( r"""(?i)foobar""" ).search( text ) is not None

Nell'applicazione:

typo = Typo()
print( typo.text_has_foobar( 'FOObar ) )

Ammetto prontamente che il mio stile è molto insolito per Python, forse anche discutibile. tuttavia, nell'esempio che corrisponde maggiormente al modo in cui Python viene utilizzato principalmente, al fine di eseguire una singola corrispondenza, è necessario creare un'istanza di un oggetto, eseguire ricerche nel dizionario di tre istanze ed eseguire tre chiamate di funzione; inoltre, potremmo avere reproblemi di memorizzazione nella cache quando utilizziamo più di 100 regex. inoltre, l'espressione regolare viene nascosta all'interno del corpo del metodo, che il più delle volte non è una buona idea.

sia detto che ogni sottoinsieme di misure --- dichiarazioni d'importazione mirate e con alias; metodi con alias ove applicabile; la riduzione delle chiamate di funzione e delle ricerche nel dizionario degli oggetti --- può aiutare a ridurre la complessità computazionale e concettuale.


2
WTF. Non solo hai tirato fuori una vecchia domanda con risposta. Il tuo codice è anche non idiomatico e sbagliato su così tanti livelli - (ab) usare le classi come spazi dei nomi in cui è sufficiente un modulo, scrivere in maiuscolo i nomi delle classi, ecc ... Vedi pastebin.com/iTAXAWen per implementazioni migliori. Per non parlare anche della regex che usi è rotta. Complessivamente, -1

2
colpevole. questa è una vecchia domanda, ma non mi dispiace essere il numero 100 in una conversazione rallentata. la domanda non è stata chiusa. ho avvertito che il mio codice potrebbe essere avverso ad alcuni gusti. penso che se potessi vederlo come una semplice dimostrazione di ciò che è fattibile in Python, tipo: se prendiamo tutto, tutto ciò in cui crediamo, come facoltativo, e poi armeggiamo in qualunque modo, che aspetto hanno le cose che possiamo ottenere? sono sicuro che puoi discernere i meriti e gli sbagli di questa soluzione e puoi lamentarti in modo più articolato. altrimenti devo concludere che la tua affermazione di torto si basa su poco più di PEP008
flusso

2
No, non si tratta di PEP8. Sono solo convenzioni di denominazione e non avrei mai votato per non aver seguito quelle. Ti ho sottovalutato perché il codice che hai mostrato è semplicemente scritto male. Sfida convenzioni e modi di dire senza motivo, ed è una incarnazione dell'ottimizzazione della permatura: dovresti ottimizzare la luce del giorno vivente da qualsiasi altro codice per farlo diventare un collo di bottiglia, e anche allora la terza riscrittura che ho offerto è più breve, più idiomatico e altrettanto veloce dal tuo ragionamento (stesso numero di accesso agli attributi).

"scritto male" - come mai esattamente? "sfida le convenzioni e gli idiomi" - ti avevo avvertito. "senza motivo" - sì, ho un motivo: semplificare dove la complessità non serve a nulla; "incarnazione dell'ottimizzazione precoce" - sono molto interessato a uno stile di programmazione che sceglie un equilibrio tra leggibilità ed efficienza; L'OP ha chiesto di ottenere "vantaggi nell'uso di re.compile", che intendo come una domanda sull'efficienza. "(ab) usare le classi come spazi dei nomi" - sono le tue parole ad essere offensive. la classe è lì, quindi hai un punto di riferimento "auto". ho provato ad usare i moduli per questo scopo, le classi funzionano meglio.
flusso

"capitalizzare i nomi delle classi", "No, non si tratta di PEP8" - apparentemente sei così scandalosamente arrabbiato da non poter nemmeno dire cosa battibellare prima. "WTF", " sbagliato " --- vedi quanto sei emotivo? più obiettività e meno schiuma per favore.
flusso

-5

La mia comprensione è che questi due esempi sono effettivamente equivalenti. L'unica differenza è che nel primo, è possibile riutilizzare l'espressione regolare compilata altrove senza farla compilare nuovamente.

Ecco un riferimento per te: http://diveintopython3.ep.io/refactoring.html

Chiamare la funzione di ricerca dell'oggetto modello compilato con la stringa 'M' compie la stessa cosa che chiamare re.search sia con l'espressione regolare che con la stringa 'M'. Solo molto, molto più veloce. (In effetti, la funzione re.search compila semplicemente l'espressione regolare e chiama il metodo di ricerca dell'oggetto modello risultante per te.)


1
non ti ho votato a fondo, ma tecnicamente questo è sbagliato: Python non si ricompilerà comunque
Trittico
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.