The first (.*?) Will match between > and I , and since it's lazy, it will immediately check the next part of the regular expression: (?P<color>red)? but there is no red at this point, so is 0 an option ? is 'activated', and the regex continues the next part, which is (.*?) . It will again match the part between > and I , and since it is lazy, it will check the next part of the regular expression: <\/span> (I take it as a whole).
So, the second (.*?) Will correspond to all that is.
In fact, your results[1] will be empty, like results[color] (I donβt remember if you need to quote color or not), and results[3] will contain I love my red car. .
Hmm, one way is to use OR, as mentioned in his NickC answer. Another that you can use is to use a negative lookup for each character:
<span>((?:(?!\bred\b).)*(?<colour>\bred\b)?.*)<\/span>
demo version of regex101
As a side note, I would suggest using word boundaries so that you don't match things like reduce or jarred .
Jerry source share