Regular Expression: Include Text After (...) Group - python

I am learning about regular expressions. I need to match things in a parenthesis group followed by some pattern that I define. When I try this with regular expressions (in Python), it only returns the part in parentheses that it matched, but not the pattern which follows it. An example should clarify:
import re
s = "texttoignore_ABCABC12345_moretexttoignore"
re.findall("(ABC)+\d+", s)
When I speak of the parenthesis group, in the example above this is the "(ABC)+" part. What I intend is for it to look for one or more repetitions of the pattern in parentheses (in this case "ABC"), then the pattern after.
The problem is this: it does not return the pattern after. (In this example, it would return 'ABC', but I would want 'ABCABC12345' or 'ABC12345' or better yet '12345')
How can you include the part after the parentheses in the return value? Is this something about regular expressions or is it specific to this Python method?
Thanks!
John

The "problem" here is that rather specific behavior of re.findall
If one or more groups are present in the pattern, return a
list of groups; this will be a list of tuples if the pattern
has more than one group.
There are a few options you have here. Either make your group non-capturing:
>>> re.findall("(?:ABC)+\d+", s)
['ABCABC12345']
or use re.finditer:
>>> [m.group(0) for m in re.finditer("(ABC)+\d+", s)]
['ABCABC12345']
If you only want to find the pattern once, then #Jkdc's approach from the comments works fine.
>>> re.search("(ABC)+\d+", s).group()
'ABCABC12345'

Related

Python regular expression findall *

I am not able to understand the following code behavior.
>>> import re
>>> text = 'been'
>>> r = re.compile(r'b(e)*')
>>> r.search(text).group()
'bee' #makes sense
>>> r.findall(text)
['e'] #makes no sense
I read some already existing question and answers about capturing groups and all. But still I am confused. Could someone please explain me.
The answer is simplified in the Regex Howto
As you can read here, group returns the string matched by the Regular Expression.
group() returns the substring that was matched by the RE.
But the action of findall is justified in the documentation
If one or more groups are present in the pattern, return a list of groups; this will be a list of tuples if the pattern has more than one group
So you are getting the matched part of the capture group.
Some experiments include :
>>> r = re.compile(r'(b)(e)*')
>>> r.findall(text)
[('b', 'e')]
Here the regex has two capturing groups, so the returned values are a list of matched groups (in tuples)
When a pattern contains a capture group, findall returns only the content of the capture group and no more the whole match.
If this behaviour looks strange, it can be very useful to extract easily parts of a string in a particular context (substring before or after), especially since python re module doesn't support variable length lookbehinds.

Backreferencing in Python: findall() method output for HTML string

I am trying to learn some regular expressions in Python. The following does not produce the output I expected:
with open('ex06-11.html') as f:
a = re.findall("<div[^>]*id\\s*=\\s*([\"\'])header\\1[^>]*>(.*?)</div>", f.read())
# output: [('"', 'Some random text')]
The output I was expecting (same code, but without the backreference):
with open('ex06-11.html') as f:
print re.findall("<div[^>]*id\\s*=\\s*[\"\']header[\"\'][^>]*>(.*?)</div>", f.read())
# output: ['Some random text']
The question really boils down to: why is there a quotation mark in my first output, but not in my second? I thought that ([abc]) ... //1 == [abc] ... [abc]. Am I incorrect?
From the docs on re.findall:
If one or more groups are present in the pattern, return a list of groups; this will be a list of tuples if the pattern has more than one group.
If you want the entire match to be returned, remove the capturing groups or change them to non-capturing groups by adding ?: after the opening paren. For example you would change (foo) in your regex to (?:foo).
Of course in this case you need the capturing group for the backreference, so your best bet is to keep your current regex and then use a list comprehension with re.finditer() to get a list of only the second group:
regex = re.compile(r"""<div[^>]*id\s*=\s*(["'])header\1[^>]*>(.*?)</div>""")
with open('ex06-11.html') as f:
a = [m.group(2) for m in regex.finditer(f.read())
A couple of side notes, you should really consider using an HTML parser like BeautifulSoup instead of regex. You should also use triple-quoted strings if you need to include single or double quotes within you string, and use raw string literals when writing regular expressions so that you don't need to escape the backslashes.
The behaviour is clearly documented. See re.findall:
Return all non-overlapping matches of pattern in string, as a list of strings. The string is scanned left-to-right, and matches are returned in the order found.
If one or more groups are present in the pattern, return a list of groups; this will be a list of tuples if the pattern has more than one group. Empty matches are included in the result unless they touch the beginning of another match.
So, if you have a capture group in your regex pattern, then findall method returns a list of tuple, containing all the captured groups for a particular match, plus the group(0).
So, either you use a non-capturing group - (?:[\"\']), or don't use any group at all, as in your 2nd case.
P.S: Use raw string literals for your regex pattern, to avoid escaping your backslashes. Also, compile your regex outside the loop, so that is is not re-compiled on every iteration. Use re.compile for that.
When I asked this question I was just starting with regular expressions. I have since read the docs completely, and I just wanted to share what I found out.
Firstly, what Rohit and F.J suggested, use raw strings (to make the regex more readable and less error-prone) and compile your regex beforehand using re.compile. To match an HTML string whose id is 'header':
s = "<div id='header'>Some random text</div>"
We would need a regex like:
p = re.compile(r'<div[^>]*id\s*=\s*([\"\'])header\1[^>]*>(.*?)</div>')
In the Python implementation of regex, a capturing group is made by enclosing part of your regex in parentheses (...). Capturing groups capture the span of text that they match. They are also needed for backreferencing. So in my regex above, I have two capturing groups: ([\"\']) and (.*?). The first one is needed to make the backreference \1 possible. The use of a backreferences (and the fact that they reference back to a capturing group) has consequences, however. As pointed out in the other answers to this question, when using findall on my pattern p, findall will return matches from all groups and put them in a list of tuples:
print p.findall(s)
# [("'", 'Some random text')]
Since we only want the plain text from between the HTML tags, this is not the output we're looking for.
(Arguably, we could use:
print p.findall(s)[0][1]
# Some random text
But this may be a bit contrived.)
So in order to return only the text from between the HTML tags (captured by the second group), we use the group() method on p.search():
print p.search(s).group(2)
# Some random text
I'm fully aware that all but the most simple HTML should not be handled by regex, and that instead you should use a parser. But this was just a tutorial example for me to grasp the basics of regex in Python.

python regular expression: re.findall(r"(do|re|mi)+","mimi rere midore")

I couldn't understand why this regular expression,
re.findall(r"(do|re|mi)+","mimi rere midore"),
generates this result,
['mi', 're', 're'].
My expected result is ['mimi', 'rere', 'midore']...
However, when I use this regular expression,
re.findall(r"(?:do|re|mi)+","mimi rere midore"),
it generates the result as expected.
Can you tell me the different between two regular expressions?
Thank you.
The difference is in the capturing group. With a capturing froup, findall() returns only what was captured. Without a capturing group, the whole match is returned.
In your first example, the group only captures the two characters, repeated or not. In the second example, the whole match includes any repetitions.
The re.findall() documentation is quite clear on the difference:
Return all non-overlapping matches of pattern in string, as a list of strings. […] If one or more groups are present in the pattern, return a list of groups; this will be a list of tuples if the pattern has more than one group.
If your (do|re|mi)+ pattern is part of a larger pattern and you want findall() to only return the full repeated set of characters, use a non-capturing group for the two-letter options with a capturing group around the whole:
r'Some example text: ((?:do|re|me)+)'

Parentheses in regular expression pattern when splitting a string

I would like to know the reason behind the following behaviour:
>>> re.compile("(b)").split("abc")[1]
'b'
>>> re.compile("b").split("abc")[1]
'c'
I seems that when I add parentheses around the splitting pattern, re adds it into the split array. But why? Is it something consistent, or simply an isolated feature of regular expressions.
It's a feature of re.split, according to the documentation:
If capturing parentheses are used in pattern, then the text of all groups in the pattern are also returned as part of the resulting list.
In general, parenthesis denote capture groups and are used to extract certain parts of a string. Read more about capture groups.
In any regular expression, parentheses denote a capture group. Capture groups are typically used to extract values from the matched string (in conjunction with re.match or re.search). For details, refer to the official documentation (search for (...)).
re.split adds the matched groups in between the splitted values:
If capturing parentheses are used in pattern, then the text of all groups in the pattern are also returned as part of the resulting list.

Accessing matched substrings when substituting using regular expressions in Python

I want to match two regular expressions A and B where A and B appear as 'AB'. I want to then insert a space between A and B so that it becomes 'A B'.
For example, if A = [0-9] and B = !+, I want to do something like the following.
match = re.sub('[0-9]!+', '[0-9] !+', input_string)
But, this obviously does not work as this will replace any matches with a string '[0-9] !+'.
How do I do this in regular expressions (preferably in one line)? Or does this require several tedious steps?
Use the groups!
match = re.sub('([0-9])(!+)', r'\1 \2', input_string);
\1 and \2 indicate the first and second parenthesised fragment. The prefix r is used to keep the \ character intact.
Suppose the input string is "I have 5G network" but you want whitespace between 5 and G i.e. whenever there are expressions like G20 or AK47, you want to separate the digit and the alphabets (I have 5 G network). In this case, you need to replace a regex expression with another regular expression. Something like this:
re.sub(r'\w\d',r'\w \d',input_string)
But this won't work as the substituting string will not retain the string caught by the first regular expression.
Solution:
It can be easily solved by accessing the groups in the regex substitution. This method will work well if you want to add something to the spotted groups.
re.sub(r"(\..*$)",r"_BACK\1","my_file.jpg") and re.sub(r'(\d+)',r'<num>\1</num>',"I have 25 cents")
You can use this method to solve your question as well by capturing two groups instead of one.
re.sub(r"([A-Z])(\d)",r"\1 \2",input_string)
Another way to do it, is by using lambda functions:
re.sub(r"(\w\d)",lambda d: d.group(0)[0]+' '+d.group(0)[1],input_string)
And another way of doing it is by using look-aheads:
re.sub(r"(?<=[A-Z])(?=\d)",r" ",input_string)

Categories