IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

Simplifier un test


Sujet :

Python

  1. #1
    Membre éprouvé

    Profil pro
    Account Manager
    Inscrit en
    Décembre 2006
    Messages
    2 301
    Détails du profil
    Informations personnelles :
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Account Manager

    Informations forums :
    Inscription : Décembre 2006
    Messages : 2 301
    Par défaut Simplifier un test
    Bonjour,
    j'aurais voulu savoir si Python permettait de faire le test ci-dessous de façon plus élégante.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    while ('_' in lesatoms) or ('_{' in lesatoms)
    or ('_{{' in lesatoms) or ('^{' in lesatoms) or ('^{{' in lesatoms):

  2. #2
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonsoir,

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    import re
     
    if re.search(r"_|_{|_{{|\^{|\^{{", lesatoms)!=None:
        # c'est Ok!
    Commentaire sur cette abominable expression régulière:
    - le "|" est un "ou"
    - le "^" étant un caractère spécial, il doit être préfixé par "\"
    - le reste est composé par tes 5 sous-chaines recherchées

    Et joyeux Noël!

    Tyrtamos

  3. #3
    Membre éprouvé

    Profil pro
    Account Manager
    Inscrit en
    Décembre 2006
    Messages
    2 301
    Détails du profil
    Informations personnelles :
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Account Manager

    Informations forums :
    Inscription : Décembre 2006
    Messages : 2 301
    Par défaut
    Merci pour ce joli cadeau.

    Joyeux noël à toi aussi.

  4. #4
    Membre Expert
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    1 418
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 1 418
    Par défaut
    Je me suis amusé à chercher si on pouvait trouver une regex plus "élégante" encore.

    J'ai voulu grouper dans une seule RE1 les cas '_' et '_{' et '_{{'
    et dans une autre RE2 les cas '^{' et '^{{'
    Car il me semble que RE1|RE2 est une regex plus satisfaisante que RE1|RE2|RE3|RE4|RE5, relativement au nombre d'essais de matching, et donc à la vitesse d'exécution, effectués par le programme en utilisant une regex composée de sous-regex.
    J'ai donc d'abord écrit de façon simpliste pour RE1:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    import re
    bla = re.compile('_{{0,2}')
    ch = raw_input(' Entrer la chaine a tester : ')
    if bla.search(ch):
        print 'il y a'
    else:
        print 'rien trouvé'
    Or en testant je me suis aperçu que les chaines
    wc___jfk et wc_{{{{{jfk
    donnent évidemment un résultat 'il y a' .
    Dans le premier cas, le troisième underscore _ est détecté comme '_' suivi de zéro '{'
    et dans le second cas les '{' au delà du deuxième sont ignorés.

    J'ai alors réalisé que la condition while présentée par rambc cherche sans doute à détecter 5 et seulement 5 motifs précis:
    underscore_ unique suivi de 0 ou 1 ou 2 {
    et
    chapeau^ unique suivi de 1 ou 2 {
    et vraisemblablement aucun caractère _ ou ^ ou { devant le premier caractère.

    J'ai poursuivi sur cette base.
    Ce qui soit dit en passant entraine que la condition de rambc elle-même n'est pas correcte relativement à cet objectif (si c'est bien celui-ci).

    En effet
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    while ('_' in lesatoms) or ('_{' in lesatoms) or ('_{{' in lesatoms)
    est redondant:
    si '_{{' est dans lesatoms, il suffit de chercher'_'
    ou dit autrement: si on détecte '_' , inutile de chercher '_{{' .

    --------------------

    Donc j'ai cherché comment spécifier par une regex unique l'ensemble de ces 5 cas et uniquement ceux-ci.

    J'ai d'abord cherché pour les 3 cas '_' et '_{' et '_{{'

    J'ai eu du mal à trouver, principalement à cause du fait qu'il faut une regex qui détecte un '_' solitaire.
    Pour spécifier qu'un '_' n'est ni précédé ni suivi d'un autre '_' ,
    j'ai d'abord pensé que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    bla = re.compile('[^_]_[^_]')
    suffisait.
    [^_] spécifie un ensemble composé de tous les caractères à l'exclusion de '_'
    Je pensais qu'après c'était fastoche:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    bla = re.compile('[^_]_[^_]{{0,2}[^{]')
    Mais ça ne marche pas, notamment si le motif cherché est en début ou en fin de chaine.
    Ce qui complique aussi beaucoup l'écriture de la regex, c'est entre le '_' et les 0 ou 1 ou 2 caractèes '{'.

    C'était une impasse. Parce que je cherchais à spécifier l'absence d'un caractère précis avec des expressions [^_] ou [^{] représentant un caractère.

    On ne s'en sort finalement qu'avec

    (?<!...)
    Matches if the current position in the string is not preceded by a match for .... This is called a negative lookbehind assertion. Similar to positive lookbehind assertions, the contained pattern must only match strings of some fixed length. Patterns which start with negative lookbehind assertions may match at the beginning of the string being searched.

    (?!...)
    Matches if ... doesn't match next. This is a negative lookahead assertion. For example, Isaac (?!Asimov) will match 'Isaac ' only if it's not followed by 'Asimov'.


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    import re
    bla = re.compile('(?<!_)_(?!_){?{?(?!{)')
    while 1:
        ch = raw_input(' Entrer la chaine a tester : ')
        if bla.search(ch):
            print 'il y a'
        else:
            print 'rien trouvé'
    Nota: je préfère finalement '{?{?' à {{0,2}


    Pour les deux autres motifs
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    import re
    bla = re.compile('(?<!\^)\^(?!\^){{?(?!{)')
    while 1:
        ch = raw_input(' Entrer la chaine a tester : ')
        if bla.search(ch):
            print 'il y a'
        else:
            print 'rien trouvé'
    Il faut écrire '\^' car '^' est un caractère spécial.


    ----

    Ensuite, quand j'ai voulu regrouper les deux RE1 et RE2, je me suis dit qu'il faut sans doute aussi éviter les motifs
    '^_{'
    '_^{{'
    etc.
    Donc il faut mettre des ensembles[_^] dans les lookaround assertions.
    Ce qui donne:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    bla = re.compile('(?<![_^])_(?![_^]){?{?(?!{)|(?<![_^])\^(?![_^]){{?(?!{)')
    Il ne faut pas de '\' devant '^' dans un ensemble: entre '[' et ']' , les caractères spéciaux perdent leur nature spéciale.
    À ceci près qu'il faut quand même écrire [_^] et non pas [^_] car [^_] est interprété comme l'ensemble de tous les caractères à l'exception de l'underscore. Ce qui fait que (?![^_]) serait interprété comme: «ne devant pas être suivi de[tous les caractères sauf l'underscore]» ce qui est équivalent à «peut être suivi de l'underscore» c'est à dire le résultat inverse de ce qu'on veut !


    Je ne sais pas si on peut considérer que la regex composée ci-dessus est plus "élégante".
    Mais je pense qu'elle répond mieux à ce que tu cherches , rambc, que ta condition qui, je crois, n'est elle même pas correcte pour ce qu'il me semble que tu veux faire.
    En tous cas je me suis bien amusé et j'ai bien appris.

    J'ai mis tout ça sous forme de programme permettant de vérifier la validité de la regex composée sur des exemples de chaines:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import re
    li = ['ab_wt','ab_{wt','ab_{{wt','_wt','_{wt','_{{wt','ab_','ab_{','ab_{{',\
          'ab^wt','ab^{wt','ab^{{wt','^wt','^{wt','^{{wt','ab^','ab^{','ab^{{',\
          'saut',\
          'gh_{{{kj','_{{{kj','gh_{{{',\
          'gh^{{{kj','^{{{kj','gh^{{{',\
          'gh^_kj','gh^_{kj','gh^_{{kj','^_kj','^_{kj','^_{{kj','gh^_','gh^_{','gh^_{{',\
          'gh^_{{{kj','^_{{{kj','gh^_{{{',\
          'gh^^kj','gh^^{kj','gh^^{{kj','^^kj','^^{kj','^^{{kj','gh^^','gh^^{','gh^^{{',\
          'gh^^{{{kj','^^{{{kj','gh^^{{{',\
          'gh_^kj','gh_^{kj','gh_^{{kj','_^kj','_^{kj','_^{{kj','gh_^','gh_^{','gh_^{{',\
          'gh_^{{{kj','_^{{{kj','gh_^{{{',\
                'gh__kj','gh__{kj','gh__{{kj','__kj','__{kj','__{{kj','gh__','gh__{','gh__{{',\
          'gh__{{{kj','__{{{kj','gh__{{{']
     
    bla = re.compile(r'(?<![_^])_(?![_^]){?{?(?!{)|(?<![_^])\^(?![_^]){{?(?!{)')
    for u in li:
        if bla.search(u):
            print u+(10-len(u))*' ',' il y a'
        elif u=='saut':
            print
        else:
            print u+(10-len(u))*' ',' rien trouvé'
    -------

    Questions:

    - pourquoi peut on écrire un caractère '{' dans une regex sans le faire précéder de '\' alors que c'est un caractère spécial qui intervient dans les expressions : 'b{3,8}' signifiant 3 à 8 caractères 'b' à la suite.

    - b{1} ne signifie pas «caractère b une seule et unique fois».
    À mon avis , l'expression {1} ne sert jamais à rien, les caractères '{' et '}' ne servent que pour spécifier des répétitions. Qu'en pensez vous ?

    - à quoi sert le 'r' dans la regex re.search(r"_|_{|_{{|\^{|\^{{", lesatoms) ?
    où peut on trouver une explication sur ce genre de caractères ?

    - y a-t-il une raison qui rendrait préférable l'une ou l'autre des expressions appremment équivalentes {?{? et {{0,2} ?

  5. #5
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    J'ai essayé de répondre strictement à la question sans la remettre en cause. Mais il est vrai que la question comporte des redondances:

    - pour trouver '_' ou '_{' ou '_{{', il suffit de chercher '_'
    - pour trouver '^{' ou '^{{', il suffit de chercher '^{'

    Ce qui fait qu'on obtient le même résultat avec:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    if re.search(r"_|\^{", lesatoms)!=None:
    Et là, il me semble difficile de faire plus court...

    Il pourrait cependant y avoir une raison de conserver les 5 sous-chaines avec les expressions régulières: si, en plus de savoir qu'on en a trouvé une, on veut savoir laquelle!

    Mais dans ce cas, il faut changer l'ordre des sous-chaines, parce que si '_' est avant '_{', on ne trouvera jamais la 2ème. Il faut donc inverser, et mettre dans l'ordre:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    '_{{'  puis  '_{'  puis  '_'  puis  '\^{{'  et enfin '\^{'
    Dans ce cas, voilà ce que ça donnera:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    x = re.search(r"_{{|_{|_|\^{{|\^{", lesatoms)
    if x!=None:
        print "ok"
        print x.group(), x.start(), x.end()  
    else:
        print "non"
    x.group() donnera la sous-chaine trouvée
    x.start() donnera l'indice du 1er caractère de la sous-chaine trouvée dans lesatoms
    x.end() donnera l'indice de fin

    On aura donc:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    x.group() == lesatoms[x.start():x.end()]
    Pour le reste de tes questions, j'ai seulement un chaleureux conseil: utilise TOUJOURS le r devant les expressions régulières. Cela dit à python qu'il s'agit d'une chaine brute, à transmettre sans aucune interprétation. En particulier s'il y a un '\'. En l'absence de ce 'r', il faudrait (pour reprendre l'exemple cité):

    - préfixer le '^' par '\' pour que le caractère '^' soit effectivement recherché
    - préfixer le '\' par un 2ème '\' pour que le '\' arrive effectivement à re.search !!!

    Je vois que tu t'attaques aux expressions régulières le "couteau entre les dents", et tu fais bien: c'est une technique complexe, mais puissante, et elle dépasse largement le seul cadre de python. Le langage Perl, en particulier l'utilise beaucoup sous linux (peut-être même un peu trop ), mais on le retrouve aussi dans tous les autres langages courants: ça vaut le coup d'investir un peu de temps pour comprendre comment ça marche.

    Enfin, en ce qui me concerne, je privilégie la lisibilité à la rapidité. C'est déjà assez compliqué comme ça. Si en plus une semaine plus tard on ne comprend plus ce qu'on a fait, c'est un sacré défaut...

    Tyrtamos

  6. #6
    Membre Expert
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    1 418
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 1 418
    Par défaut
    On ne peut certes pas faire plus court que r"_|\^{" mais c'est en restant dans le cadre de la condition énoncée par rambc, et donc en continuant de détecter des motifs comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    '-{{{{{'      '-----{'      '^^^{{{{{' 
    S'il faut limiter strictement l'ensemble des motifs détectés aux 5 en question, il faudra nécessairement faire plus long.



    --------------------

    Enfin, en ce qui me concerne, je privilégie la lisibilité à la rapidité. C'est déjà assez compliqué comme ça. Si en plus une semaine plus tard on ne comprend plus ce qu'on a fait, c'est un sacré défaut...
    Comme le principe de l'informatique c'est tout de même de permettre de simplifier considérablement des taches longues et/ou ardues à un degré tel qu'elles deviennent possibles, je pense qu'il est d'autant plus intéressant de l'utiliser que les taches envisagées sont compliquées.
    Or comme Python est un langage très lisible, je pense que cette lisibilité intrinsèque permet de s'enfoncer plus loin encore dans la complexité de programmes que si on utilisait un autre langage. Je veux dire qu'il ne faut pas s'effaroucher de voir un code s'éloigner de l'aspect épuré des codes destinés à effectuer des taches simples, sous prétexte de perte de lisibilité alors que c'est justement cette lisibilité qui permet des codes un peu complexes.
    Dans le domaine touffu des regex, Python facilite la lisibilité en fournissant la possibilité d'écrire les regex avec le flag VERBOSE.


    --------------------------


    Ta remarque, tyrtamos, sur l'intérêt de conserver une regex composée de 5 sous-regex m'a fait beaucoup phosphorer et j'aboutis à plusieurs objections.


    La première est encore liée à cette histoire d'imprécision de ce qui est détecté.
    Avec
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    x = re.search(r"_{{|_{|_|\^{{|\^{", lesatoms)
    si search() rencontre en premier le motif '_{' ,
    la RE2 va le détecter, x.group() va être '_{' et on sera sûr que ce ne peut pas être "_{{" puisque RE1 n'a pas fonctionné avant RE2.
    Par contre si search() sort de son chapeau un x.group() valant '_{{', on ne saura pas si c'est parce qu'elle a rencontré '_{{' ou '_{{{{{{{{{{{{{{{'.
    En mettant "_{{|_{|_" dans la regex composée, on améliore la précision par rapport à "_|\^{" , mais ça reste une précision médiocre. Si c'est ce qui est recherché, rien à dire, mais il ne faut pas se laisser illusionner par le panel de 5 sous-RE.
    De plus, il reste que RE3 va détecter '____' , RE2 va détecter '____{' etc.

    Donc pour un objectif demandant plus de précision, il faudra de toutes façons faire appel à des RE complétées telles que
    (?<![_{^])-}}(?![_{^])
    pour éviter les signes _{et^ avant et après le motif limité à '_{{'.
    Ce qui pour l'ensemble de la regex composée donnerait:
    r"(?<![_{^])-}}(?![_{^])|(?<![_{^])-}(?![_{^])|(?<![_{^])-(?![_{^])|(?<![_{^])^}(?![_{^])|(?<![_{^])^}}(?![_{^])"
    Pour 5 sous-RE, ça va encore, mais on voit bien que ça devient vite impraticable et illisible si ce nombre augmente.


    La deuxième objection est que Python arrive parfaitement à dire quel est le motif rencontré par search() sans avoir à détailler des sous-RE, comme le montre le programme suivant:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import re
     
    bla = re.compile(r"_{?{?")
     
    ch = 'jg_hghg ^_{jhg'
    x = re.search(bla,ch)
    print 'x.group(), x.start(), x.end() =',x.group(), x.start(), x.end()
     
    ch = 'jg_{hghg ^_{jhg'
    x = re.search(bla,ch)
    print 'x.group(), x.start(), x.end() =',x.group(), x.start(), x.end()
     
    ch = 'jg_{{hghg ^_{jhg'
    x = re.search(bla,ch)
    print 'x.group(), x.start(), x.end() =',x.group(), x.start(), x.end()
    qui sort
    x.group(), x.start(), x.end() = _ 2 3
    x.group(), x.start(), x.end() = _{ 2 4
    x.group(), x.start(), x.end() = _{{ 2 5



    La troisième objection est qu'on peut vouloir trouver dans une chaine toutes les occurences de chacun des motifs d'un ensemble de motifs différents alors que search() s'arrête de chercher à la première occurence de motif rencontrée.
    Dans ce cas, on est bien obligé de se débrouiller,
    avec autre chose que search() d'une part,
    et surtout en n'ayant pas à écrire explicitement tous les motifs virtuellement envisagés par combinaison de caractères ou groupes de base, le nombre de motifs résultant pouvant très vite devenir rédhibitoire.

    Donc, j'ai cherché, et il m'a fallu garder le couteau entre les dents parce que j'ai eu du mal à piger certains trucs.

    Mais voilà, je suis arrivé à un joli petit programme de démonstration qui cherche 5 sortes de motifs dans une chaine.

    Normalement, en taille 10 de police, les tabulations que j'ai mises doivent donnner une présentation des résultats en colonnes bien alignées.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import re
     
    bla = re.compile(r"""
       (?<![_{^])   (_)     (?![_{^])   # detecte un underscore solitaire ni précédé ni suivi de ^ ou {
    |  (?<![_{^]) ([_^]{{?) (?![{_^])   # detecte les cas [_^]{{? non précédé de [_{^] et non suivi de [
    |  (?<![_{^])   (;\d)   (?![_{^])   # detecte ;0  ;1  ;2  ;3  etc
    |               (;7X)               # detecte ;7X
    |            (a(y)?:5)              # detecte ay:5 et a:5
    # NB: avec VERBOSE, les blancs qui ne sont pas entre crochets ne participent pas a la definition des regex
    """,re.VERBOSE)
     
    ch = 'jg_{hgay:5yas_{{TTYhg ^{k_hkj__{jfa:5ygh_{{hs;6gf_{{{{gfgf^_{hg^^jfg;7Xzez^{_{{hgf^{{kjh'
     
    # mob = MatchObject
    print '\nliste des matchings et renseignements associés :'
    print '\tgroups()\t\t\tgroup()\tgroup(0)  group(1)     group(2)  span()'
    for mob in re.finditer(bla,ch):
        print mob.groups(),' \t',mob.group(),' \t ',mob.group(0),'\t  ',mob.group(1),'   \t',mob.group(2),'\t',mob.span()
    re.finditer(bla,ch) renvoie un itérateur qui fournit tous les MatchObjects obtenus dans l'exploration de la chaine ch avec la regex bla.
    Sur chaque mob = MatchObject fourni par finditer() , on peut faire agir groups() pour afficher la façon dont a réagi chaque groupe présent dans la regex quand celle-ci détecte une sous-chaine qui matche.


    J'ai remarqué que contrairement à search() où ce n'est pas nécessaire, il faut que dans la regex chaque motif décrit par une sous-RE soit entre parenthèses pour que la trace de la détection du motif correspondant soit enregistré par groups(). Sans cela, le matching du motif en sous-chaine et du motif dans la sous-RE a bien lieu quand même, mais la sous-chaine détectée n'est pas enregistrée dans le tuple groups() .

    En réalité, ceci provient du fait que
    - d'une part
    «Groups are numbered starting with 0. Group 0 is always present; it's the whole RE, so MatchObject methods all have group 0 as their default argument. »
    http://www.amk.ca/python/howto/regex/
    Je ne sais pas pourquoi il appelle ça "whole RE", il s'agit en fait de la sous-chaine détectée

    - et d'autre part
    «groups( [default])
    Return a tuple containing all the subgroups of the match, from 1 up to however many groups are in the pattern.»

    http://www.python.org/doc/2.5/lib/match-objects.html#l2h-426

    Donc, quel que soit ce qu'on trouve dans groups() en fonction de la façon dont on a écrit la regex, la détection d'un motif a toujours lieu et il est enregistré dans group(0) auquel renvoie le défaut d'argument dans group() .
    Voilà pourquoi quand on utilise search() , x.group() donne le motif détecté, qui est unique et se trouve dans x.group(0) , même s'il n'y a pas de parenthèses autour du motif dans la regex.
    Le code suivant met ceci en évidence:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import re
     
    bla = re.compile(r"""
       (?<![_{^])   (_)     (?![_{^])   # detecte un underscore solitaire ni précédé ni suivi de ^ ou {
    |  (?<![_{^]) [_^]{{? (?![{_^])   # detecte les cas [_^]{{? non précédé de [_{^] et non suivi de [
    |  (?<![_{^])   ;\d   (?![_{^])   # detecte ;0  ;1  ;2  ;3  etc
    |                ;7X              # detecte ;7X
    |            a(y)?:5              # detecte ay:5 et a:5
    # NB: avec VERBOSE, les blancs qui ne sont pas entre crochets ne participent pas a la definition des regex
    """,re.VERBOSE)
     
    ch = 'jg_{hgay:5yas_{{TTYhg ^{k_hkj__{jfa:5ygh_{{hs;6gf_{{{{gfgf^_{hg^^jfg;7Xzez^{_{{hgf^{{kjh'
     
    x = re.search(bla,ch)
    print 'x.groups(),x.group(),x.start(),x.end() =',x.groups(),x.group(), x.start(), x.end()
     
    print
     
    # mob = MatchObject
    print '\nliste des matchings et renseignements associés :'
    print '  groups()\tgroup()\tgroup(0)  group(1)     group(2)  span()'
    for mob in re.finditer(bla,ch):
        print mob.groups(),'  \t',mob.group(),' \t ',mob.group(0),'\t  ',mob.group(1),'   \t',mob.group(2),'\t',mob.span()


    Les matchings peuvent être obtenus directement par re.findall() sans passer par les MatchObjects donnés par l'itérateur re.finditer():
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import re
     
    bla = re.compile(r"""
       (?<![_{^])   (_)     (?![_{^])   # detecte un underscore solitaire ni précédé ni suivi de ^ ou {
    |  (?<![_{^]) ([_^]{{?) (?![{_^])   # detecte les cas [_^]{{? non précédé de [_{^] et non suivi de [
    |  (?<![_{^])   (;\d)   (?![_{^])   # detecte ;0  ;1  ;2  ;3  etc
    |               (;7X)               # detecte ;7X
    |            (a(y)?:5)              # detecte ay:5 et a:5
    # NB: avec VERBOSE, les blancs qui ne sont pas entre crochets ne participent pas a la definition des regex
    """,re.VERBOSE)
     
    ch = 'jg_{hgay:5yas_{{TTYhg ^{k_hkj__{jfa:5ygh_{{hs;6gf_{{{{gfgf^_{hg^^jfg;7Xzez^{_{{hgf^{{kjh'
     
    # mob = MatchObject
    print '\nliste des matchings et renseignements associés :'
    print '\tgroups()\t\t\tgroup()\tgroup(0)  group(1)     group(2)  span()'
    for mob in re.finditer(bla,ch):
        print mob.groups(),' \t',mob.group(),' \t ',mob.group(0),'\t  ',mob.group(1),'   \t',mob.group(2),'\t',mob.span()
     
    print '\nliste des matchings et des span() :'
    for u in re.findall(bla,ch):
        print u
    Pour obtenir des '' dans les matchings au lieu de 'None' en passant par re.finditer(), il faut écrire

    Si l'on veut obtenir seulement les sous-chaines qui matchent en utilisant re.findall() , il faut faire:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import re
     
    bla = re.compile(r"""
       (?<![_{^])   (_)     (?![_{^])   # detecte un underscore solitaire ni précédé ni suivi de ^ ou {
    |  (?<![_{^]) ([_^]{{?) (?![{_^])   # detecte les cas [_^]{{? non précédé de [_{^] et non suivi de [
    |  (?<![_{^])   (;\d)   (?![_{^])   # detecte ;0  ;1  ;2  ;3  etc
    |               (;7X)               # detecte ;7X
    |            (a(y)?:5)              # detecte ay:5 et a:5
    # NB: avec VERBOSE, les blancs qui ne sont pas entre crochets ne participent pas a la definition des regex
    """,re.VERBOSE)
     
    ch = 'jg_{hgay:5yas_{{TTYhg ^{k_hkj__{jfa:5ygh_{{hs;6gf_{{{{gfgf^_{hg^^jfg;7Xzez^{_{{hgf^{{kjh'
     
    # mob = MatchObject
    print '\nliste des matchings et renseignements associés :'
    print '\tgroups()\t\tgroup()\tgroup(0)  group(1)     group(2)  span()'
    for mob in re.finditer(bla,ch):
        print mob.groups(''),' \t',mob.group(),' \t ',mob.group(0),'\t  ',mob.group(1),'   \t',mob.group(2),'\t',mob.span()
     
    print '\nliste des matches par re.findall() :'
    limatches = [ ma for matching in re.findall(bla,ch) for ma in matching if ma ]
    for ma in limatches:
        print ma
    C'est un peu plus compliqué, et moins lisible: je préfère passer par le recours à re.finditer() qui est plus efficace et plus lisible, et permet d'avoir facilement d'autres renseignements en plus.
    Malgré cela, les deux méthodes ne nécessitent pas de détailler de façon exhaustive toutes les sous-RE à la main.

  7. #7
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Si on veut détecter un '_{' mais en interdisant des motifs comme '___{{{{', il suffit d'ajouter les conditions avant et après le motif cible:

    Condition 'avant':

    ( = début d'un groupe
    [^_] = n'importe quel caractère, sauf '_'
    | = ou
    ^ = début de chaine
    ) = fin du groupe

    Condition 'après':

    ( = début d'un groupe
    [^{] = n'importe quel caractère, sauf '{'
    | = ou
    $ = fin de chaine
    ) = fin du groupe

    Il est de plus intéressant de mettre le motif '_{'entre parenthèses pour former un groupe.

    Ce qui donnerait pour ce seul motif à chercher:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    x = re.search(r"([^_]|^)(_{)([^{]|$)", lesatoms)
    Ce motif répond à l'objectif qu'on s'est donné: il détecte '_{' dans xxx_{yyy' mais pas dans 'xxx___{{{yyy'.

    Et effectivement, si on fait cela pour les 5 motifs, l'expression régulière se complique un peu:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    x = re.search(r"(([^_]|^)(_{{)([^{]|$))|(([^_]|^)(_{)([^{]|$))|(([^_]|^)(_)([^_{]|$))|(([^\^]|^)(\^{{)([^{]|$))|(([^\^]|^)(\^{)([^{]|$))", lesatoms)
    On pourrait la rendre un peu plus courte (mais pas plus simple!) en "mettant en facteur" les motifs qui se répètent.

    Par ailleurs, cela complique un peu la recherche du motif qui a déclenché la réussite du test, à cause de la multitude des groupes qu'on a construit avec les parenthèses.

    Les résultats de x.start() et x.end() donnent toujours les indices du motif trouvé, mais cette sous-chaine intègre à présent le caractère avant et après le motif recherché (comme par exemple 'x_{y' en milieu de chaine, ou 'x_{' en fin de chaine, ou '_{y en début de chaine), ce qui complique un peu l'identification du motif trouvé.

    Bien sûr, cela ne peut plus être comparé avec la question de départ car on introduit une contrainte supplémentaire: le test initial "while ... '_{' in lesatoms ..." n'empêche pas les motifs de type: "____{{{{{".

    A part cela, juste une petite remarque: si tu choisis de compiler un motif (ce qui suppose que tu t'en sers plusieurs fois), il faut utiliser le résultat de cette compilation:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    bla = re.compile(r"_{?{?")
    ch = 'jg_hghg ^_{jhg'
     
    x = bla.search(ch)  #  et pas: x = re.search(bla,ch)
    Tyrtamos

  8. #8
    Membre Expert
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    1 418
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 1 418
    Par défaut
    Bien sûr, cela ne peut plus être comparé avec la question de départ car on introduit une contrainte supplémentaire
    Tout à fait. Mais en étendant le problème, on fait à la fois une réponse à l'incohérence de la condition initiale et on aborde des aspects plus complexes des regex. C'est à la fois instructif et plus amusant.


    Pour la notation
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    x = bla.search(ch)
     au lieu de
    x = search(bla,ch)
      avec bla = regex compilée
    je savais bien mais j'ai voulu garder un formalisme identique aux écritures re.search(r"_|\^{", lesatoms) et re.search(r"_{{|_{|_|\^{{|\^{", lesatoms) que tu as employées avec des regex simples, pour ne perturber en rien la comparaison des procédés. Et comme je ne pouvais alourdir les expressions en mettant la regex qui définit bla avec sa typographie VERBOSE dans les parenthèses de search() findall() et finditer() et que search(bla,ch) findal(bla,ch) et finditer(bla,ch) marchent même si bla est compilée, j'ai pris cette notation batarde.
    Dans les cas réels, j'utiliserai effectivement la notation bla.search(ch).




    J'ai été très content de découvrir et de comprendre les notations (?=...) (?<=...) (?!...) (?<!...) parce qu'elles apportent une solution parfaite dans certains cas. Il est clair qu'on est dans un de ces cas ici puisque en ne les utilisant pas, on retombe dans des problèmes.


    - J'ai moi aussi pensé un moment faire suivre les motifs recherchés d'un groupe contenant $ et à les faire précéder d'un groupe contenant ^.
    Mais utiliser par exemple $ c'est parce que le cas où le motif est en fin de chaine ne peut pas être décrit par le choix pris pour décrire les autres cas, à savoir qu'en milieu de chaine on AUTORISE LA PRÉSENCE après le motif d'un caractère QUI NE DOIT PAS ÊTRE TEL OU TEL.

    Or on veut en fait INTERDIRE tout caractère QUI EST TEL OU TEL aprés un motif.

    "Non autoriser ceci" est logiquement équivalent à "Interdire non ceci" , mais les traductions en instruction ne sont pas équivalentes et je préfère personnellement utiliser (?!{) pour interdire '{' aprés un motif, sans se préoccuper de savoir si celui-ci est suivi ou non d'un caractère.


    - L'ennui avec ta regex, c'est aussi que si on veut éliminer des motifs comme '^_{_' , il faut rajouter des caractères dans ta regex, et qu'elle en devient aussi longue que celle avec des lookaround assertions:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    import re
    ch = 'jg_{_hgay:5yas_{^TTYhg ^{k_hkj__{jfa:5ygh_{{hs;6gf_{gfgf^_{hg^^jfg;7Xzez'
    x = re.search(r"([^_{^]|^)(_{)([^{_^]|$)", ch)
    print x.group(),x.span()
    x = re.search(r"(?<![_{^])(_{)(?![{_^])", ch)
    print x.group(),x.span()
    donne
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    f_{g (49, 53)
    _{ (50, 52)
    EDIT:
    j'avais oublié les crochets autour de _{^ dans les lokkaround assertions, ce qui rendait à tort ma regex plus courte que celle de tyrtamos,
    et surtout 2 résultats différents




    - Autre conséquence de ton écriture, tu la fais toi-même remarquer, l'intervalle span() trouvé est plus étendu que celui du motif recherché.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    import re
    ch = 'jg_{Mhgay'
    x = re.search(r"([^_]|^)(_{)([^{]|$)", ch)
    print x.group(),x.span()
    x = re.search(r"(?<!_)(_{)(?!{)", ch)
    print x.group(),x.span()
    donne
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    g_{M (1, 5)
    _{ (2, 4)
    Conséquence:
    cela complique un peu la recherche du motif qui a déclenché la réussite du test, à cause de la multitude des groupes qu'on a construit avec les parenthèses.
    Ben oui. En fait, ta regex ne détecte tout simplement pas les mêmes motifs qu'une regex utilisant (?=...) (?<=...) (?!...) (?<!...).


    J'ai mis toute une après midi à comprendre qu'il me fallait un truc comme les lookaround assertions (lookahead et lookbehind assertions) pour résoudre ce problème, à découvrir que ça existait dans Python et à les comprendre. Maintenant que je les tient, je pense vraiment qu'elles sont géniales et irremplaçables dans certains cas.


    On pourrait la rendre un peu plus courte (mais pas plus simple!) en "mettant en facteur" les motifs qui se répètent.
    Tu es donc bien d'accord avec moi: si on veut faire quelque chose, on est bien obligé d'utiliser les ressources du langage, même si on perd de la lisibilité. En fait il faut savoir ce qu'on veut: rester lisible ou arriver à faire un programme même quand il est complexe ?

  9. #9
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Citation Envoyé par eyquem Voir le message
    C'est à la fois instructif et plus amusant.
    Ça, c'est sûr! Et merci de prendre le problème comme tu le prends: même si je ne te suis pas sur tous les points, c'est stimulant et enrichissant.

    J'ai procédé à 3 améliorations de mon code:

    - au lieu de '[^_]|^' pour indiquer: soit un caractère (sauf '_') soit le début de ligne, on peut indiquer '[^_]?', qui veut dire: soit un caractère (sauf '_'), soit rien. Cela donne le même résultat. Idem pour la fin de ligne: '[^{]?'.

    - pour identifier le motif qui a déclenché la réussite du test, on peut indiquer dans les motifs "avant" et "après" qu'ils ne doivent pas "consommer" du texte. On indique cela par '(?=...)', ce qui donne par exemple: '(?=[^_]?)' au lieu de '([^_]?)'. Ce qui fait que le motif trouvé par x.group() et x.span() ne comporte aucun des caractères supplémentaires "avant" et "après", tout en participant à la condition.

    - finalement, on n'a pas besoin d'entourer les motifs cherchés par des parenthèses.

    Par contre, je ne fais toujours pas les "mises en facteurs" des motifs qui se répètent, parce que ça devient trop compliqué à lire et à gérer.

    Mon code devient donc (et c'est ma meilleure proposition):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    x = re.search(r"((?=[^_]?)_{{(?=[^{]?))|((?=[^_]?)_{(?=[^{]?))|((?=[^_]?)_(?=[^_{]?))|((?=[^\^]?)\^{{(?=[^{]?))|((?=[^\^]?)\^{(?=[^{]?))", lesatoms)
    if x!=None:
        print "ok"
        print "motif trouvé:", x.group(), "emplacement:", x.span()
    else:
        print "non"
    Tyrtamos

  10. #10
    Membre Expert
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    1 418
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 1 418
    Par défaut
    Ta regex ne marche toujours pas bien.

    Je prends le cas de la première sous-RE:
    (?=[^_]?)_{{(?=[^{]?)
    --------------------
    Considérons d'abord ce qui se passe en fonction de la partie écrite après le motif '_{{' recherché.

    J'ai comparé les résultats d'une lookahead assertion (?=...) et ceux d'une lookahead assertion (?=...?) situées, pour bien voir ce qui se passe, aprés deux caractères '4Y' en faisant tourner le code suivant:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import re
     
    def cherche(n,pat,ch):
        print n+"chaine = '"+ch+"'",
        x = re.search(pat,ch)
        if x:  print "\tmotif", x.group(), "trouvé à l'emplacement", x.span()
        else:  print "\tpas de match"
     
    bla1 = '4Y(?=[^{])'
    print "regex1 =",bla1
    cherche(' 1/',bla1,'cd4Y:jhg')
    cherche(' 2/',bla1,'cd4Y{jhg')
    cherche(' 3/',bla1,'cd4Y')
     
    print
     
    bla2 = '4Y(?=[^{]?)'
    print "regex2 =",bla2
    cherche(' 1/',bla2,'cd4Y:jhg')
    cherche(' 2/',bla2,'cd4Y{jhg')
    cherche(' 3/',bla2,'cd4Y')
    Les résultats sont:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    regex1 = 4Y(?=[^{])
     1/chaine = 'cd4Y:jhg' 	motif 4Y trouvé à l'emplacement: (2, 4)
     2/chaine = 'cd4Y{jhg' 	pas de match
     3/chaine = 'cd4Y' 	pas de match
    
    regex2 = 4Y(?=[^{]?)
     1/chaine = 'cd4Y:jhg' 	motif 4Y trouvé à l'emplacement: (2, 4)
     2/chaine = 'cd4Y{jhg' 	motif 4Y trouvé à l'emplacement: (2, 4)
     3/chaine = 'cd4Y' 	motif 4Y trouvé à l'emplacement: (2, 4) 
    Avec la regex1, les résultats 1 et 2 sont ceux recherchés (ils discriminent correctement les motifs différents).
    Le résultat 3 n'est pas celui recherché, c'est ce qui a conduit à essayer la regex2.

    Avc la regex 2, le résultat 3 devient conforme à ce qu'on recherchait (trouver le motif en bout de chaine), le résultat 2 reste celui recherché.
    Mais le résultat 1 devient complétement inintéressant car le motif est déclaré trouvé alors qu'il n'est pas dans la chaine.

    L'expression (?=[^{]?) que j'appelle LA2 (comme lookahead) ne fait donc pas du tout ce qu'on en espérait: elle ne discrimine pas des motifs différents qu'elles est censée distinguer.
    Dans ce code, LA2 est appelée à fonctionner quand elle est positionnée sur l'atome-intercaractères situé juste aprés le caractère 'Y' , c'est à dire pour examiner l'atome-caractère juste à l'aval de 'Y'.
    Dans LA2, [^_] indique que cet atome-caractère doit être un non-'_', mais le '?' indique que si cette condition n'est pas vérifiée ce n'est pas grave car il est permis qu'elle ne soit pas vérifiée. Ainsi LA2 accepte aussi bien de trouver un caractère non-'_', aucun caractère du tout ou un caractère '_ à l'aval de 'Y'. Autant dire qu'elle n'est pas contrariante mais qu'elle ne sert strictement à rien, les résultats 1 et 2 étant identiques, avec la regex2.


    Appliquée à notre sujet, l'inadéquation de cette expression a les conséquences mises en évidences par le code suivant :


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import re
     
    def cherche(n,pat,ch):
        print n+"chaine = '"+ch+"'",
        x = re.search(pat,ch)
        if x:  print "\tmotif", x.group(), "trouvé à l'emplacement", x.span()
        else:  print "\tpas de match"
     
    bla1 = "_{{(?=[^{])"
    bla2 = "_{{(?=[^{]?)"
     
    print "\nAvec  la regex =",bla1,'\n'
    cherche('',bla1,'asd_{{hg')
    print "                        Résultat recherché\n"
    cherche('',bla1,'asd_{{{{hg')
    print "                        Résultat recherché\n"
    cherche('',bla1,'asd_{{')
    print "                        Ce n'est pas ce qu'on veut"
     
     
    print '\n\n\nAvec  la regex =',bla2,'\n'
    cherche('',bla2,'asd_{{ki')
    print "                        Ceci reste OK = résultat recherché\n"
    cherche('',bla2,'asd_{{')
    print "                        Cette fois, ceci est le résultat recherché\n"
    cherche('',bla2,'asd_{{{{hg')
    print "                        Mais c'est cette détection qui ne va plus\n"

    Résultat:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    Avec  la regex = _{{(?=[^{]) 
    
    chaine = 'asd_{{hg' 	motif _{{ trouvé à l'emplacement (3, 6)
                            Résultat recherché
    
    chaine = 'asd_{{{{hg' 	pas de match
                            Résultat recherché
    
    chaine = 'asd_{{' 	pas de match
                            Ce n'est pas ce qu'on veut
    
    
    
    Avec  la regex = _{{(?=[^{]?) 
    
    chaine = 'asd_{{ki' 	motif _{{ trouvé à l'emplacement (3, 6)
                            Ceci reste OK = résultat recherché
    
    chaine = 'asd_{{' 	motif _{{ trouvé à l'emplacement (3, 6)
                            Cette fois, ceci est le résultat recherché
    
    chaine = 'asd_{{{{hg' 	motif _{{ trouvé à l'emplacement (3, 6)
                            Mais c'est cette détection qui ne va plus
    
    >>>
    -----------------------------------------


    Pour ce qui est maintenant de la partie écrite avant le motif '_{{' recherché:



    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import re
    def cherche(pat,ch):
        print "chaine = '"+ch+"'",
        x = re.search(pat,ch)
        if x:  print "\tmotif", x.group(), "trouvé à l'emplacement:", x.span()
        else:  print "\tpas de match"
     
    rex1 = "(?=[^_])_{{"
    print '\n\n* Avec la  rex1 =',rex1,'\n'
    cherche(rex1,'asd_{{zw')
    cherche(rex1,'asd___{{zw')
    cherche(rex1,'_{{zw')
    print "Car LA1 est toujours devant '_'"
    Résultat:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    * Avec la  rex1 = (?=[^_])_{{ 
    
    chaine = 'asd_{{zw' 	pas de match
    chaine = 'asd___{{zw' 	pas de match
    chaine = '_{{zw' 	pas de match
    Car LA1 est toujours devant '_'

    J'appelle LA1 = (?=[^_])
    Dans tous les cas, rex1 (sans '?' à la fin de LA1) ne détecte pas le motif:
    c'est parce que quand LA1 fait un examen de ce qui est l'aval de sa situation à ce moment là, elle est toujours positionnée à la position juste devant un '_' . Je ne sais pas si elle "s'arrête" pour faire cet examen devant tout underscore '_' qu'elle rencontre, ou seulement devant ceux d'un motif '_{{' . Peu importe en fait: tout examen de l'aval par LA1 se fait quand elle est devant un '_', donc avec LA1 devant ça ne matche jamais, c'est normal.







    Pour obtenir un match sur la chaine 'asd_{{zw' , il semble naturel d' essayer ensuite une regex rex2 = (?=[^_]?)_{{ dans laquelle il y a LA2 = (?=[^_]?)
    Brrrrr....

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import re
    def cherche(pat,ch):
        print "chaine = '"+ch+"'",
        x = re.search(pat,ch)
        if x:  print "\tmotif", x.group(), "trouvé à l'emplacement:", x.span()
        else:  print "\tpas de match"
     
    rex2 = "(?=[^_]?)_{{"
    print '\n\n* Avec la  rex2 =',rex2,'\n'
    cherche(rex2,'asd_{{zw')
    cherche(rex2,'asd___{{zw')
    cherche(rex2,'_{{zw')
    print "Car '?' dans LA2 rend facultatif un char non-'_' a la position de '_'"
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    * Avec la  rex2 = (?=[^_]?)_{{ 
    
    chaine = 'asd_{{zw' 	motif _{{ trouvé à l'emplacement (3, 6)
    chaine = 'asd___{{zw' 	motif _{{ trouvé à l'emplacement (5, 8)
    chaine = '_{{zw' 	motif _{{ trouvé à l'emplacement (0, 3)
    Car '?' dans LA2 rend facultatif un char non-'_' a la position de '_'
    
    
    Cette fois, ça semble être bon, le motif est bien détecté , qu'il soit à l'intérieur d'une chaine ou à son début.
    Mais le même problème que pour les caractères postérieurs au motif surgit: ça détecte aussi le motif quand il est dans '_____{{'.

    En réalité LA2 est une expression complétement transparente:
    (?=[^_]?)c est strictement pareil que c .Ceci peut se voir en forçant LA2 à travailler devant un caractère quelconque et en constatant que le résultat est indépendant du caractère. Pour ça, il suffit de mettre un caractère repère devant elle pour fixer la position à laquelle elle va agir, comme 'V' dans l'exemple suivant:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    rexV2 = "V(?=[^_]?)"
    print '\n\n* Avec la  rexV2 =',rexV2,'\n'
    cherche(rexV2,'asdV_{{zw')
    cherche(rexV2,'asdVa{{zw')
    cherche(rexV2,'asdV:{{zw')
    cherche(rexV2,'asdV!{{zw')
    cherche(rexV2,'asdV {{zw')
    cherche(rexV2,'asdV{{{zw')
    Resultat:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    * Avec la  rexV2 = V(?=[^_]?) 
    
    chaine = 'asdV_{{zw' 	motif V trouvé à l'emplacement (3, 4)
    chaine = 'asdVa{{zw' 	motif V trouvé à l'emplacement (3, 4)
    chaine = 'asdV:{{zw' 	motif V trouvé à l'emplacement (3, 4)
    chaine = 'asdV!{{zw' 	motif V trouvé à l'emplacement (3, 4)
    chaine = 'asdV {{zw' 	motif V trouvé à l'emplacement (3, 4)
    chaine = 'asdV{{{zw' 	motif V trouvé à l'emplacement (3, 4)




    On pourrait alors se dire que si rex2 détecte bien le motif '_{{' quand il est seul, et qu'elle le détecte trop bien encore quand il est précédé de plusieurs underscores '_', on peut essayer de la "retenir" pour lui faire examiner l'aval de la chaine dès le premier caractère susceptible d'être un '_', au moyen d'une lettre préalable comme ce 'V".

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import re
    def cherche(pat,ch):
        print "chaine = '"+ch+"'",
        x = re.search(pat,ch)
        if x:  print "\tmotif", x.group(), "trouvé à l'emplacement", x.span()
        else:  print "\tpas de match"
    rexH2 = "H(?=[^_]?)_{{"
    print '\n\n* Avec la  rexH2 =',rexH2,'\n'
    cherche(rexH2,'asdH_{{zw')
    cherche(rexH2,'asdH__{{zw')
    cherche(rex1,'_{{zw')
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    * Avec la  rexH2 = H(?=[^_]?)_{{ 
    
    chaine = 'asdH_{{zw' 	motif H_{{ trouvé à l'emplacement (3, 7)
    chaine = 'asdH__{{zw' 	pas de match
    chaine = '_{{zw' 	pas de match

    En effet ça marche.
    Apparemment.
    Car pour faire ça il faut introduire une lettre comme 'H' en amont de [COLOR="Teal"]LA2[/COLOR], c'est à dire connaître déjà cet amont.....
    Et puis si le motif est en début de chaine, il n'est pas détecté....

    Plein de problèmes.
    Donc
    (?=[^_]?) est vraiment inutilisable pour écrire la partie de la regex devant le motif.

    -------------

    Peut on espérer en (?<=[^_])_{{ ??
    Ben non, on veut pouvoir détecter le motif même s'il est au début de la chaine.


    Peut on espérer alors en (?<=[^_]?)_{{ ??
    Python ne le permet pas....

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import re
    def cherche(pat,ch):
        print "chaine = '"+ch+"'",
        x = re.search(pat,ch)
        if x:  print "\tmotif", x.group(), "trouvé à l'emplacement", x.span()
        else:  print "\tpas de match"
    retro2 = "(?<=[^_]?)_{{"
    print '\n\n* Avec la  retro2 =',retro2,'\n'
    cherche(retro2,'asdH_{{zw')
    cherche(retro2,'asdH__{{zw')
    cherche(retro2,'_{{zw')
    Résultat

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    ]* Avec la  retro2 = (?<=[^_]?)_{{ 
    
    chaine = 'asdH_{{zw'
    Traceback (most recent call last):
      File "E:/Python/Essais Python/tyrt retro2.py", line 9, in <module>
        cherche(retro2,'asdH_{{zw')
      File "E:/Python/Essais Python/tyrt retro2.py", line 4, in cherche
        x = re.search(pat,ch)
      File "C:\Python25\lib\re.py", line 134, in search
        return _compile(pattern, flags).search(string)
      File "C:\Python25\lib\re.py", line 233, in _compile
        raise error, v # invalid expression
    error: look-behind requires fixed-width pattern
    >>> 


    Vraiment, il n'y a rien d'autre à faire que d'utiliser les lookaround NEGATIVE assertions.
    Il y a pire comme situation. Vive Python !



    Maintenant, je crois que je peux enlever le couteau d'entre les dents. Ça me sera utile pour le réveillon.

  11. #11
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Tu as raison, j'avais fait des tests, mais pas suffisamment!

    Voilà ma dernière proposition (les chaines de test sont dans la liste A):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    for lesatoms in A:
        x = re.search(r"(((?<=[^_])|^)_{{(?=[^{]|$))|(((?<=[^_])|^)_{(?=[^{]|$))|(((?<=[^_])|^)_(?=[^_{]|$))|(((?<=[^\^])|^)\^{{(?=[^{]|$))|(((?<=[^\^])|^)\^{(?=[^{]|$))", lesatoms)
        if x!=None:
            print u"motif trouvé:", x.group(), u"emplacement:", x.span()
        else:
            print u"non"
    Et voilà les résultats que ça donne (j'ai mis le résultat affiché en commentaire dans chaque ligne):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
     
    A = []
    # 1er élément _{{
    A.append("_{{xxxxxxxxx") # motif trouvé: _{{ emplacement: (0, 3)
    A.append("xx_{{xxxxxxx") # motif trouvé: _{{ emplacement: (2, 5)
    A.append("x__{{xxxxxxxxx") # non
    A.append("xx_{{{xxxxxxxxx") # non
    A.append("xx_{{xxx_{xxxx_xx^{xxx^{{xx") # motif trouvé: _{{ emplacement: (2, 5)
     
    # 2ème élément _{
    A.append("_{xxxxxxxxx") # motif trouvé: _{ emplacement: (0, 2)
    A.append("xx_{xxxxxxx") # motif trouvé: _{ emplacement: (2, 4)
    A.append("x__{xxxxxxxxx") # non
    A.append("xx_{{{{xxxxxxxxx") # non
    A.append("xx_{xxx_{{xxxx_xx^{xxx^{{xx") # motif trouvé: _{ emplacement: (2, 4)
     
    # 3ème élément _
    A.append("_xxxxxxxxx") # motif trouvé: _ emplacement: (0, 1)
    A.append("xx_xxxxxxx") # motif trouvé: _ emplacement: (2, 3)
    A.append("x__xxxxxxxxx") # non
    A.append("xx_xxx_{{xxxx_{xx^{xxx^{{xx") # motif trouvé: _ emplacement: (2, 3)
     
    # 4ème élément ^{{
    A.append("^{{xxxxxxxxx") # motif trouvé: ^{{ emplacement: (0, 3)
    A.append("xx^{{xxxxxxx") # motif trouvé: ^{{ emplacement: (2, 5)
    A.append("x^^{{xxxxxxxxx") # non
    A.append("xx^{{{xxxxxxxxx") # non
    A.append("xx^{{xxx_{{xxxx_xx^{xxx^{{xx") # motif trouvé: ^{{ emplacement: (2, 5)
     
    # 5ème élément ^{
    A.append("^{xxxxxxxxx") # motif trouvé: ^{ emplacement: (0, 2)
    A.append("xx^{xxxxxxx") # motif trouvé: ^{ emplacement: (2, 4)
    A.append("x^^{xxxxxxxxx") # non
    A.append("xx^{{{{xxxxxxxxx") # non
    A.append("xx^{xxx_{{xxxx_xx^{xxx^{{xx") # motif trouvé: ^{ emplacement: (2, 4)
    Le message d'erreur que tu as trouvé en dernier vient du fait que le "(?<=...)" exige que le motif ait un nombre constant de caractères. Pour échapper à cela, il suffit de faire: ((?<=[^_])|^), c'est à dire de sortir le "début de chaine" du bloc.

    Je suis revenu en arrière sur le remplacement de |^ et |$ par des ?. Manifestement, ce n'était pas une bonne idée.

    Finalement, la solution initiale de rambc n'était pas mal

    Bon réveillon !!!

    Tyrtamos

  12. #12
    Membre Expert
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    1 418
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 1 418
    Par défaut
    Finalement, après t'être converti à l'intérêt d'une lookahead assertion (?=...) dans ton avant-dernier message, je vois que tu finis par utiliser aussi une lookbehind assertion (?<=...) dans ton dernier.
    Tu confirmes ainsi ce que j'ai écrit:
    «Vraiment, il n'y a rien d'autre à faire que d'utiliser les lookaround NEGATIVE assertions»
    puisqu'elles sont strictement équivalentes aux expressions que tu écris:
    ((?<=[^_])|^) c'est (?<!_)
    ((?=[^{])|$) c'est (?!{)
    «Manifestement, ce n'était pas une bonne idée» d'abandonner les notations |^ et |_ , mais pouquoi vouloir les garder ?

    On peut augmenter le nombre de caractères pour couvrir tous les caractères vraisemblablement à exclure autour du motif, sans complexifier outre mesure :
    (?<![_^{]) et (?![_^{]).

    En outre, si on distingue le motif '_' des 4 autres, il n'est pas si compliqué de factoriser les motifs '_{' '_{{' '^{' '^{{' ,pour aboutir finalement à une regex très condensée:
    (?<![_^{])_(?![_^{])|(?<![_^{])[_^]{{?(?![_^{])
    Qui était déjà dans mon message #6.

    J'ai peut être poussé un peu loin le décorticage au couteau à certains moments, mais je ne suis pas mécontent d'être parvenu à toucher l'os.
    Ce sujet va faire comme l'année: il se termine, mais il va être remplacé par d'autres.

  13. #13
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bravo pour le dernier motif!

    Et merci pour ton opiniâtreté. Manifestement, il porte de bons fruits.

    J'ai pu aussi à cette occasion constater à quel point la notice n'était pas - et de très loin - à la hauteur des possibilités de la technique. On découvre les fonctionnalités petit à petit par des tests nombreux et systématiques. Bref, on perd un peu son temps à redécouvrir ce que le programmeur savait forcément en programmant.

    Bonnes fêtes!

    Tyrtamos

  14. #14
    Membre Expert
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    1 418
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 1 418
    Par défaut
    La notice ?
    Tu parles de
    http://www.python.org/doc/2.5/lib/module-re.html
    ?

    Je trouve aussi que les explications y sont un peu trop concentrées et qu'elles ne pointent pas assez l'importance de notions hyper fondamentales qui permettent d'acquérir une vision globale dans laquelle il est ensuite plus facile de comprendre des nouveautés.
    Par exemple, remarquer qu'il existe deux formes
    x = re.search(pat,ch)
    x = pat.search(ch)
    dans un cas il y a une fonction et dans l'autre il s'agit d'une méthode attachée à pat.
    Les explications sur groups() aussi : je suis resté dans le brouillard même après les avoir lues 10 fois.

    Et Bonnes Fêtes aussi.

Discussions similaires

  1. Simplifier un test avec arguments
    Par dodo91 dans le forum Langage
    Réponses: 3
    Dernier message: 10/06/2009, 16h07
  2. Script test de deux chaine avec if
    Par kacedda dans le forum Linux
    Réponses: 6
    Dernier message: 02/05/2003, 15h38
  3. [XMLRAD] test de nullité
    Par Pm dans le forum XMLRAD
    Réponses: 5
    Dernier message: 29/11/2002, 10h57
  4. test collisions
    Par tatakinawa dans le forum OpenGL
    Réponses: 5
    Dernier message: 08/06/2002, 06h03

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo