2010-03-29 3 views
3

Je suis très nouveau sur Python, et relativement nouveau sur regex. (Je n'ai aucune expérience Perl.)Comment améliorer ma syntaxe Python regex?

Je suis capable d'utiliser des expressions régulières d'une manière qui fonctionne, mais je ne suis pas sûr que mon code soit particulièrement Pythonien ou concis. Par exemple, si je voulais lire dans un fichier texte et imprimer le texte qui apparaît directement entre les mots «foo» et «bar» dans chaque ligne (en supposant que cela se produisait une ou zéro fois une ligne), j'écrirais ce qui suit:

fileList = open(inFile, 'r') 
pattern = re.compile(r'(foo)(.*)(bar)') 
for line in fileList: 
    result = pattern.search(line) 
    if (result != None): 
     print result.groups()[1] 

Y a-t-il un meilleur moyen? Le if est nécessaire pour éviter d'appeler groups() sur None. Mais je soupçonne qu'il y a une façon plus concise d'obtenir la chaîne correspondante quand il y en a une, sans lancer d'erreur quand il n'y en a pas. Je ne souhaite pas l'illisibilité Perl-like. Je veux juste accomplir cette tâche commune de la manière la plus commune et la plus simple.

Répondre

3

Je pense que c'est bon.

Quelques points mineurs: -

  • Vous pouvez remplacer result.groups()[x] avec result.group(x+1).
  • Si vous n'avez pas besoin de capturer foo et bar, utilisez simplement r'foo(.*)bar'.
  • Si vous utilisez Python 2.5+, essayez d'utiliser the with statement donc même en cas d'exception, le fichier peut être fermé correctement.

BTW, comme 5-liner (pas que je recommande ce):

import re 
pattern = re.compile(r'foo(.*)bar') 
with open(inFile, 'r') as fileList: 
    searchResults = (pattern.search(line) for line in fileList) 
    groups = (result.group(1) for result in searchResults if result is not None) 
    print '\n'.join(groups) 
+0

Pour une raison quelconque, 'result.group (1)' capture 'foo' pour moi, mais' result.group (2) 'fonctionne. –

+0

@FarmBoy: Parce que vous faites correspondre avec '(foo) (. *) (Bar)' au lieu de 'foo (. *) Bar'. – kennytm

+0

L'index d'un tuple ne serait-il pas basé sur 0? Je m'attendais 'result.group (0)' retournerait 'foo' dans mon code. –

0

vous n'avez pas besoin regex. divisez votre chaîne sur "bar", parcourez-les, trouvez "foo", faites un split sur "foo" et obtenez les résultats à droite. Bien sûr, vous pouvez utiliser d'autres manipulations de chaînes comme obtenir l'index et d'autres choses.

>>> s="w1 w2 foo what i want bar w3 w4 foowhatiwantbar w5" 
>>> for item in s.split("bar"): 
...  if "foo" in item: 
...   print item.split("foo")[1:] 
... 
[' what i want '] 
['whatiwant'] 
1

Il y a deux tours à avoir: la première est la fonction d'expression régulière re.finditer (et méthode). La seconde est l'utilisation du module mmap.

De la documentation sur re.DOTALL, nous pouvons noter que . ne correspond pas: les nouvelles lignes

sans ce drapeau, '' va correspondre à tout sauf un retour à la ligne.

Donc, si vous regardez pour tous les matches partout dans le fichier (par exemple lors de la lecture dans une chaîne à l'aide f.read()), vous pouvez faire semblant chaque ligne est une sous-chaîne isolée (note:. Il est pas tout à fait vrai, mais si vous voulez que les assertions^et $ fonctionnent de cette façon, utilisez re.MULTILINE). Maintenant, parce que vous avez noté que nous supposons qu'il n'y a que zéro ou une occurrences par ligne, nous n'avons pas à nous inquiéter de ce que re.finditer() corresponde plus qu'il ne le devrait (parce qu'il le ferait!).Alors tout de suite, vous pouvez remplacer tout cela avec plus finditer itérer() au lieu:

fileList = open(inFile, 'r') 
pattern = re.compile(r'foo(.*)bar') 
for result in pattern.finditer(fileList.read()): 
    print result.groups(1) 

Ce n'est pas vraiment joli. Le problème ici est que le fichier entier est lu dans la mémoire pour votre commodité. Ce serait bien s'il y avait un moyen pratique de le faire sans éventuellement casser des fichiers plus gros. Et, bien, il y en a! Entrez le module mmap. Mmap vous permet de traiter un fichier comme s'il s'agissait d'une chaîne (une chaîne mutable, pas moins!), Et il ne charge pas l'ensemble de la mémoire en mémoire. La longue et courte est, vous pouvez utiliser le code suivant à la place:

fileList = open(inFile, 'r+b') 
fileS = mmap.mmap(fileList.fileno(), 0) 
pattern = re.compile(r'foo(.*)bar') 
for result in pattern.finditer(fileS): 
    print result.groups(1) 

et il fonctionnera tout de même, mais sans consommer tout le fichier à la fois (je l'espère).

0

J'ai quelques suggestions mineures:

  • Sauf si vous êtes certain que foo et bar peut se produire plus d'une fois par ligne, il est préférable d'utiliser .*? au lieu de .*
  • Si vous devez assurez-vous que foo et bar ne doivent correspondre comme des mots entiers (par opposition à foonly et rebar), vous devez ajouter \b points d'ancrage autour d'eux (\bfoo\b etc.)
  • Vous pouvez utiliser lookaround pour faire correspondre uniquement la correspondance elle-même ((?<=\bfoo\b).*?(?=\bbar\b)), alors maintenant result.group(0) contiendra la correspondance. Mais ce n'est pas vraiment plus lisible :)