Шукаць шматслоўным радок у спісе радкоў

У адрозненне ад Perl, вы не можаце да маіх ведаў адпавядае рэгулярнаму выразу ўнутры, калі заяву ў Python і прысвоіць вынік Назва зменнай, у той жа час. Гэта прыводзіць да тыповых канструктаў, як гэта:

match = re.search(REGEX, STRING)
if match:
    # do something

Да гэтага часу, так што Python. Але што, калі я хачу, каб перабіраць файл/масіў радкоў, праверыць кожную радок на некалькі рэгулярных выразаў, і агонь злавіць-ўсё, калі ні адзін не адпавядае? Я не магу думаць свой шлях вакол даволі грувасткія і глыбока ўкладзеных калі-то яшчэ, калі-інакш ...- будаўніцтваў:

import re
strings = ["abc zzz", "y", "#comment"]
for s in strings:
    match = re.search("(\S+) (\S+)", s)
    if match:
        print "Multiword: %s+%s" % (match.group(1), match.group(2))
    else:
        match = re.match("y$", s)
        if match:
            print "Positive"
        else:
            match = re.match("n$", s)
            if match:
                print "Negative"
            else:
                # a few more matches possible in real life script,
                # and then the last catch-all:
                print "No match found, line skipped"

Ці ня ёсьць спосаб зрабіць гэта ў значна больш прыемна, гледзячы Эліф-будаўніцтва або што-то? Ніжэй не працуе ў Python, таму што калі-пункты прымаюць толькі выразы, а не заявы. Тым не менш, што-то ўздоўж гэтых ліній ударыць мяне, як прарочыя, ці я сьляпы да нечага відавочнаму тут?

if match = re.search(" ", s):
    print "Multiword: %s+%s" % (match.group(1), match.group(2))
elif match = re.match("y$", s):
    print "Positive"
else:
    print "No match found, line skipped"
32

9 адказы

Проста выкарыстоўвайце працягнуць ключавое слова, каб прымусіць прасоўванне ацаніць наступны радок. Код пасля таго, калі аператар будзе выконвацца толькі калі папярэдняе заява была ілжывым.

import re
strings = ["abc zzz", "y", "#comment"]
for s in strings:
    match = re.search("(\S+) (\S+)", s)
    if match:
        print "Multiword: %s+%s" % (match.group(1), match.group(2))
        continue
    match = re.match("y$", s)
    if match:
        print "Positive"
        continue
    match = re.match("n$", s)
    if match:
        print "Negative"
        continue

    # a few more matches possible in real life script,
    # and then the last catch-all:
    print "No match found, line skipped"
28
дададзена
@Thor адзіная праблема з гэтай структурай, якую я неяк быццам бы, што гэта так лёгка забыцца дадаць працягнуць у новым пункце дадаць пазней
дададзена аўтар halloleo, крыніца
З аднаго боку, гэта не выглядае ідэальна «чысты» для мяне, з другога боку, гэта лёгка зразумець, і я б сказаў, што б трымаць лёгка падтрымліваць. Я павінен буду праверыць гэта крыху больш глыбокі заўтра, але дзякуй за адказ!
дададзена аўтар starchx, крыніца

Чаму не выкарыстоўваючы спіс картэжаў (рэ, лямбда матчу: дзеянне) , то ёсць нешта накшталт

actions = [("(\S+) (\S+)", lambda match: "Multiword: %s+%s" % (match.group(1), match.group(2))),
           ("y$", lambda match: "Positive"),
           ("n$", lambda match: "Negative")]

потым:

for rex, action in actions:
     match = re.match(rex, s)
     if match: 
          print action(match)

Калі вам трэба змяшаць пошук і матч, то вы можаце выкарыстоўваць спіс картэжаў:

(matchmethod, rex, action)

як ў

actions = [
    (re.search, "(\S+) (\S+)", lambda match: "Multiword: %s+%s"%(match.group(1), match.group(2)) ),
    (re.match, "y$", lambda match: "Positive"),
    (re.match, "n$", lambda match: "Negative")]

І, вядома ж:

for matchtype, rex, action in actions:
     match = matchtype(rex, s)
     if match: 
          print action(match)
14
дададзена
@RemcoGerlich: Раствор можа быць лёгка адаптаваны. Глядзіце маё рэдагаванне.
дададзена аўтар General Nuisance, крыніца
@thor: гэта проста штурхаючы далей пітона перамыкач заяву
дададзена аўтар General Nuisance, крыніца
Прапускае той факт, што адзін з выклікаў павінен быў re.search, іншы re.match.
дададзена аўтар yak, крыніца
Я ведаю, і гэта пэўны метад. Я думаю, што гэта будзе залежаць ад колькасці тэстаў і колькі іх код мае ў агульным, для прыняцця рашэння, калі гэта добрая ідэя. Увогуле, я думаю, што гэта робіць код менш чытаным, але ў яго ёсць патэнцыял, каб зрабіць гэта нашмат карацей, і гэта добра.
дададзена аўтар yak, крыніца
Гэта значна больш складаныя ў разуменні, то я прывык, але гэта здаецца даволі прарочым. Я паэксперыментаваць з гэтым, дзякуй!
дададзена аўтар starchx, крыніца

Я б паставіў яго ў функцыі і зваротнага з яго, калі супадзенне знойдзена, што, як вы не маеце ўсе водступы для яшчэ: выпадкі, проста спіс тэстаў і вяртае для іх:

import re
strings = ["abc zzz", "y", "#comment"]

def run_tests(s)
    match = re.search("(\S+) (\S+)", s)
    if match:
        print "Multiword: %s+%s" % (match.group(1), match.group(2))
        return

    if re.match("y$", s):
        print "Positive"
        return

    if re.match("n$", s):
        print "Negative"
        return

    # a few more matches possible in real life script,
    # and then the last catch-all:
    print "No match found, line skipped"

for s in strings:
    run_tests(s)

Я хацеў бы паспрабаваць паставіць спіс тэстаў у некаторых структуры дадзеных перабраць (напрыклад, паведамленні і шаблоны для праверкі), а таму, што код трохі адрозніваецца (пошук супраць матчу, проста надрукаваць супраць рабіць нешта з матчам) гэта ясней проста захаваць яго, як гэта.

12
дададзена
Вы заўсёды можаце зрабіць свой код калі заўгодна складаным, ды :-). У такіх выпадках магчыма ўсё зменныя павінны быць часткай нейкага дзяржаўнага класа, а зменная стану можа быць прыняты да набору test_ функцый, ці ж яны могуць быць метады гэтага класа, або і так далей.
дададзена аўтар yak, крыніца
Гэта працуе добра, пакуль я не хачу, каб запоўніць розныя зменныя пад рознымі запалкамі - то я заўсёды павінен прайсці поўны спіс магчымых зменных назад і наперад паміж гэтай функцыяй і выклікаюць, ці не так?
дададзена аўтар starchx, крыніца

Мне падабаецца @ падыход Ивер, але было б фармалізаваць гэта крыху больш:

import re

tests = [
    ("(\S+) (\S+)", "Multiword: {0}+{1}"),
    ("^y$",         "Positive"),
    ("^n$",         "Negative")
]

def get_first_match(s, tests=tests, none_match="No match found, line skipped"):
    for reg,fmt in tests:
        match = re.search(reg, s)
        if match:
            return fmt.format(*match.groups())
    return none_match

то

strings = ["abc zzz", "y", "#comment"]
for s in strings:
    print(get_first_match(s))
6
дададзена
@Izkata: вось чаму я папярэднічаецца А «^» для іх - гэта прымушае .search паводзіць сябе як .match (адпавядае толькі ў пачатку радка).
дададзена аўтар Sarvesh Mishra, крыніца
Першы быў re.search , іншыя re.match
дададзена аўтар Yashas, крыніца

Для таго, каб пайсці яшчэ далей на падыходах прапанавалі паставіць рэгулярныя выразы ў спісе вы можаце далучыцца да рэгулярных выразаў разам з | а затым супаставіць лінію супраць усіх магчымых мадэляў на адным дыханні.

import re

class LineMatcher(object):
    def __init__(self, patterns):
        # In order to match each string, we build a regex combining which can match
        # all the parts. 
        # Something like: ((\S+) (\S+))|(y$)|(n$)|(.*))
        # When we match against it, we can figure out which pattern was matched
        self._groups = {}
        regexes = []

        # because groups could contain groups, we need to keep track of the current
        # group index so that we know which index each pattern will end up with.
        current_group_index = 1
        for pattern, handler in patterns:
            group_count = re.compile(pattern).groups
            self._groups[current_group_index] = (group_count, handler)
            regexes.append("(%s)" % pattern)
            current_group_index += group_count + 1

        self._regex = re.compile("|".join(regexes))

    def match(self, string):
        match = self._regex.match(string)
        group_count, handler = self._groups[match.lastindex]
        captures = match.groups()[match.lastindex:match.lastindex + group_count]
        return handler(*captures)


matcher = LineMatcher([
    ("(\S+) (\S+)", lambda first, second: "Multiword: %s+%s"),
    ("y$", lambda: "Positive"),
    ("n$", lambda: "Negative"),
    (".*", lambda: "No match found, line skipped")
])


strings = ["abc zzz", "y", "#comment"]
for s in strings:
    print matcher.match(s)
5
дададзена
@Gabe, на самай справе ў маім кодзе вышэй яго не выключаюць адзін аднаго. Не тое, што. * Адпавядае любому. Я думаю, што ў выпадку невыразнасці яна заўсёды вяртае першы матч.
дададзена аўтар Mark Cidade, крыніца
Варта адзначыць, што гэты падыход працуе толькі таму, што рэгулярныя выразы з'яўляюцца ўсе ўзаемна выключаюць адзін аднаго. Калі вам трэба, каб гэта было мульты-слова <�я> і </я> канец у «у», напрыклад, гэта не можа быць выкарыстаны.
дададзена аўтар kenny, крыніца
Мне падабаецца гэтае рашэнне, таму што пазбаўляе ад неабходнасці паўторна выканаць пошук назваў паведамленняў для кожнай магчымасці; вы яшчэ такое ж колькасць умоўных, па сутнасці, але каротказамкнутым праверкі
дададзена аўтар Stefan Berg, крыніца
Мне падабаецца гэтае рашэнне, гэта здаецца вельмі прарочы, які пашыраецца і «чысцей», чым працягнуць -подходе з @Calpratt. З іншага боку, я магу зразумець, што падыход у першым поглядзе кода, у той час як тут я павінен працаваць свой шлях праз яго. Я думаю, цяпер маё пытанне: калі вы натыкнуліся на нешта накшталт гэтага ў кодзе, вы павінны падтрымліваць, што б зрабіць жыццё прасцей для вас? Маючы гэта на ўвазе, я буду выбіраць гру- але прасцей працягнуць -подходе.
дададзена аўтар starchx, крыніца

Calpratt's answer without continue:

import re
strings = ["abc zzz", "y", "#comment"]
for s in strings:
    match = re.search("(\S+) (\S+)", s)
    if match:
        print "Multiword: %s+%s" % (match.group(1), match.group(2))
    elif re.match("y$", s):
        print "Positive"
    elif re.match("n$", s):
        print "Negative"
    else:
        print "No match found, line skipped"
5
дададзена
Так, але гэта працуе толькі менавіта для гэтай сітуацыі. З першага Эліф года ў вас няма шанцаў атрымаць доступ да выніку матчу. Там нават небяспека таго, каб спрабаваць атрымаць доступ да запалкі аб'екта ў Elif галіны, якія прывялі б да працы над памылковымі вынікамі. Няма downvote, так як гэта не з'яўляецца няправільным само па сабе, але гэта не так гнуткі, як і іншыя падыходы.
дададзена аўтар starchx, крыніца

Гэта вядомая праблема з рэгулярнымі выразамі. Але вы можаце проста пакласці параметры ў кантэйнеры, напрыклад, спіс, а затым выкарыстоўваць для цыкла:

import re
strings = ["abc zzz", "y", "#comment"]
regexps_and_messages = [
    ("(\S+) (\S+)", "Multiword: %s+%s"),
    ("y$", "Positive"),
    ("n$", "Negative"),
]

for s in strings:
    for regexp, message in regexps_and_messages:
        m = re.match(regexp, s)
        if m is not None:
            print message % m.groups()
            break
    else: # if no break in above loop
        print "No match found, line skipped"
5
дададзена
@Thor не вельмі, калі вам трэба паводзіны матч толькі PREPEND ^ прыдатныя рэгулярных выразаў, напрыклад, <�Код> "^ у $" і "^ п $" . Паводзіны будзе трохі адрознівацца ад <�запалкавай/кода> пры выкарыстанні сцяга шматрадковых рэгулярных выразаў (ён таксама будзе адпавядаць пасля кожнай новай радкі замест толькі ў пачатку радка шукаецца), але, як вы праверкай лініі -па лініі ўсё адно, што не варта рабіць адрозненне (і ў тых выпадках, $ будзе адпавядаць перад любой новай радком, а таму вам прыйдзецца турбавацца аб тым, што адзін таксама, калі такое паводзіны не пажадана) ,
дададзена аўтар Stefan Berg, крыніца
Не тое, што мне асабліва патрэбен re.match супраць re.search адрозненне, але гэта робіць яго немагчыма ўтрымаць. Акрамя таго, для ... астатняе будаўніцтва, на маю вопыту, так рэдка, што большасць суправаджаюць, не ведаў бы адразу, што гэта робіць - ці гэта проста маё ўласнае невуцтва казаць?
дададзена аўтар starchx, крыніца

Пасля таго, як вы прызналі шаблон ў вашым кодзе, вы можаце абгарнуць яго ў якасці базавага класа, каб пазбегнуць Макеты. Гэта таксама робіць код больш рамонтапрыдатнасць.

import re

class Handler:
    PATTERN = ''
    def __init__(self):
        self._pattern = re.compile(self.PATTERN)

    def match(self, *args, **kwargs):
        return self._pattern.match(*args, **kwargs)

    def handle(self, matched):
        pass

class MultiwordHandler(Handler):
    PATTERN = '(\S+) (\S+)'

    def match(self, *args, **kwargs):
        return self._pattern.search(*args, **kwargs)

    def handle(self, matched):
        print 'Multiword: %s+%s' % (matched.group(1), matched.group(2))

class PositiveHandler(Handler):
    PATTERN = 'y$'

    def handle(self, matched):
        print 'Positive'

class NegativeHandler(Handler):
    PATTERN = 'n$'

    def handle(self, matched):
        print 'Negative'

І выкарыстоўваць іх, як гэта:

handlers = [MultiwordHandler(), PositiveHandler(), NegativeHandler()]

strings = ["abc zzz", "y", "#comment"]

for s in strings:
    for handler in handlers:
        matched = handler.match(s)
        if matched:
            handler.handle(matched)
            break
    else:
        print "No match found, line skipped"
3
дададзена

Вы можаце зрабіць паток прыкладання больш лінейная альбо зваротнага -ную з метаду пры адсутнасці або зрабіць гэта Python спосаб , выкарыстоўваючы Выключэнні , напрыклад.:

try:
    if not do_action_1:
        raise Exception('Action 1 failed')
    if not do_action_2:
        raise Exception('Action 2 failed')
    if not do_action_3:
        raise Exception('Action 3 failed')
    if not do_action_4:
        raise Exception('Action 4 failed')
    if not do_action_5:
        raise Exception('Action 5 failed')
except Exception as e:
    print 'Whoops: %s' % e
2
дададзена
Я б назваў гэта злоўжыванне выключэннямі.
дададзена аўтар yak, крыніца
Акрамя адчування, што я павінен пагадзіцца з @RemcoGerlich на гэта, я таксама павінен прызнаць, што я не зусім разумею, як прымяніць гэта да маёй сітуацыі. З'яўляюцца do_action_x -части ўсе павінны быць асобныя функцыі, адзін для кожнага матчу я спрабую праверыць?
дададзена аўтар starchx, крыніца