31

I've been trying to match the following string:

string = "TEMPLATES = ( ('index.html', 'home'), ('base.html', 'base'))"

But unfortunately my knowledge of regular expressions is very limited, as you can see there are two parentheses that need to be matched, along with the content inside the second one I tried using re.match("\(w*\)", string) but it didn't work, any help would be greatly appreciated.

Jakub Hampl
  • 39,863
  • 10
  • 77
  • 106
Paulo
  • 6,982
  • 7
  • 42
  • 56

5 Answers5

37

Try this:

import re
w = "TEMPLATES = ( ('index.html', 'home'), ('base.html', 'base'))"

# find outer parens
outer = re.compile("\((.+)\)")
m = outer.search(w)
inner_str = m.group(1)

# find inner pairs
innerre = re.compile("\('([^']+)', '([^']+)'\)")

results = innerre.findall(inner_str)
for x,y in results:
    print("%s <-> %s" % (x,y))

Output:

index.html <-> home
base.html <-> base

Explanation:

outer matches the first-starting group of parentheses using \( and \); by default search finds the longest match, giving us the outermost ( ) pair. The match m contains exactly what's between those outer parentheses; its content corresponds to the .+ bit of outer.

innerre matches exactly one of your ('a', 'b') pairs, again using \( and \) to match the content parens in your input string, and using two groups inside the ' ' to match the strings inside of those single quotes.

Then, we use findall (rather than search or match) to get all matches for innerre (rather than just one). At this point results is a list of pairs, as demonstrated by the print loop.

Update: To match the whole thing, you could try something like this:

rx = re.compile("^TEMPLATES = \(.+\)")
rx.match(w)
Jonas Adler
  • 10,365
  • 5
  • 46
  • 73
phooji
  • 10,086
  • 2
  • 38
  • 45
  • thanks for the reply, but is it possible to match the whole string including the "TEMPLATES = " part so that i can replace the whole thing with another string ? – Paulo Mar 18 '11 at 20:56
  • @paulo: I've added a regex that matches the entire string. – phooji Mar 18 '11 at 21:05
  • thank you so much, i really appreciate your help , and thanks all those who contributed :D – Paulo Mar 18 '11 at 21:09
  • Good point, @akaRem -- `str` is the built-in typename for strings, and re-using that name is probably unwise. I've updated the answer to use `w` instead of `str`. – phooji Apr 29 '12 at 01:41
  • @akaRem "Certainly many people don't realize that these esoteric built-ins even exist, and they would be surprised if they were prevented from using them as variable names. From here, it's just a gradual path. Many people write functions or methods with arguments named str or len, or with names like compile or format." [The History of Python](http://python-history.blogspot.hu/2013/11/story-of-none-true-false.html) – 3k- Dec 08 '13 at 12:28
  • @3k- I'm not native speaker, so it's hard to understand, do you agree with me, or not.. – akaRem Dec 09 '13 at 13:19
  • @akaRem I agree, however the quote says that Python was intentionally designed in a way so ppl can override builtins. str is a builtin function, not a keyword. a keyword is like `if` and you cannot override `if`. – 3k- Dec 09 '13 at 18:02
  • @3k- What if everybody will override everything many times at any places? No, thanks. – akaRem Dec 11 '13 at 14:17
16

First of all, using \( isn't enough to match a parenthesis. Python normally reacts to some escape sequences in its strings, which is why it interprets \( as simple (. You would either have to write \\( or use a raw string, e.g. r'\(' or r"\(".

Second, when you use re.match, you are anchoring the regex search to the start of the string. If you want to look for the pattern anywhere in the string, use re.search.

Like Joseph said in his answer, it's not exactly clear what you want to find. For example:

string = "TEMPLATES = ( ('index.html', 'home'), ('base.html', 'base'))"
print re.findall(r'\([^()]*\)', string)

will print

["('index.html', 'home')", "('base.html', 'base')"]

EDIT:

I stand corrected, @phooji is right: escaping is irrelevant in this specific case. But re.match vs. re.search or re.findall is still important.

Community
  • 1
  • 1
Vojislav Stojkovic
  • 8,043
  • 4
  • 35
  • 48
  • what i want to do is match the string "TEMPLATES = ( ('index.html', 'home'), ('base.html', 'base'))" and replace it with another string, is there a way to match the "TEMPLATES = " part along with the parenthesis ? btw thanks for the explanation – Paulo Mar 18 '11 at 20:48
  • Actually `re.match("\(hello\)", "(hello)")` works just fine, although I agree that it is generally easier to always use `r"..."` for regular expression literals. – phooji Mar 18 '11 at 21:00
  • @paulo: What are you trying to do with that match, verify the format? – Vojislav Stojkovic Mar 18 '11 at 21:09
  • basically im opening a django settings file and matching a specific string and replacing its contents – Paulo Mar 18 '11 at 21:16
3

If your strings look like valid Python code anyways you can do this:

import ast
var, s = [part.strip() for part in 
     "TEMPLATES = ( ('index.html', 'home'), ('base.html', 'base'))".split('=')]
result= ast.literal_eval(s)
Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
1

Your sample is looking for open paren followed by zero or more letter w followed by close paren. You probably want to use \w instead of w, but that won't work in your case anyway, because you have non-word characters next to the open paren.

I think you should consider splitting the string at the commas instead. What is your final objective?

Joseph Bui
  • 1,701
  • 15
  • 22
0

In case you want to validate that parentheses are balanced up two levels deep, you can use this regular expression:

import re;

string = """( ('index.html', 'home'), ('base.html', 'base'))
('index.html', 'home')
('base.html', 'base')
"""

pattern = re.compile(r"(?P<expression>\(([^()]*(?P<parenthesis>\()(?(parenthesis)[^()]*\)))*?[^()]*\))")

match = pattern.findall(string)

print(match[0][0])
print(match[1][0])
print(match[2][0])

This regular expression uses conditional statement (?(parenthesis)[^()]*\)).

Demo: https://repl.it/@Konard/ParenthesesExample

Konard
  • 2,298
  • 28
  • 21