.* will match any character (including newlines if dotall is used). This is greedy: it matches as much as it can.
(.*) will add that to a capture group.
(.*?) the ? makes the .* non-greedy, matching as little as it can to make a match, and the parenthesis makes it a capture group as well.
For example:
>>> import re
>>> txt = ''' foo
... bar
... baz '''
>>> for found in re.finditer('(.*)', txt):
... print found.groups()
...
(' foo',)
('',)
('bar',)
('',)
('baz ',)
('',)
>>> for found in re.finditer('.*', txt):
... print found.groups()
...
()
()
()
()
()
()
>>> for found in re.finditer('.*', txt, re.DOTALL):
... print found.groups()
...
()
()
>>> for found in re.finditer('(.*)', txt, re.DOTALL):
... print found.groups()
...
(' foo\nbar\nbaz ',)
('',)
And since the ? matches as little as possible, we match empty strings:
>>> for found in re.finditer('(.*?)', txt, re.DOTALL):
... print found.groups()
...
('',)
('',)
('',)
('',)
('',)
('',)
('',)
('',)
('',)
('',)
('',)
('',)
('',)
('',)