Issue #16203: Add re.fullmatch() function and regex.fullmatch() method,
which anchor the pattern at both ends of the string to match.

Original patch by Matthew Barnett.
diff --git a/Lib/re.py b/Lib/re.py
index b41aab0..77f5e3f 100644
--- a/Lib/re.py
+++ b/Lib/re.py
@@ -85,16 +85,17 @@
     \\       Matches a literal backslash.
 
 This module exports the following functions:
-    match    Match a regular expression pattern to the beginning of a string.
-    search   Search a string for the presence of a pattern.
-    sub      Substitute occurrences of a pattern found in a string.
-    subn     Same as sub, but also return the number of substitutions made.
-    split    Split a string by the occurrences of a pattern.
-    findall  Find all occurrences of a pattern in a string.
-    finditer Return an iterator yielding a match object for each match.
-    compile  Compile a pattern into a RegexObject.
-    purge    Clear the regular expression cache.
-    escape   Backslash all non-alphanumerics in a string.
+    match     Match a regular expression pattern to the beginning of a string.
+    fullmatch Match a regular expression pattern to all of a string.
+    search    Search a string for the presence of a pattern.
+    sub       Substitute occurrences of a pattern found in a string.
+    subn      Same as sub, but also return the number of substitutions made.
+    split     Split a string by the occurrences of a pattern.
+    findall   Find all occurrences of a pattern in a string.
+    finditer  Return an iterator yielding a match object for each match.
+    compile   Compile a pattern into a RegexObject.
+    purge     Clear the regular expression cache.
+    escape    Backslash all non-alphanumerics in a string.
 
 Some of the functions in this module takes flags as optional parameters:
     A  ASCII       For string patterns, make \w, \W, \b, \B, \d, \D
@@ -123,7 +124,7 @@
 import sre_parse
 
 # public symbols
-__all__ = [ "match", "search", "sub", "subn", "split", "findall",
+__all__ = [ "match", "fullmatch", "search", "sub", "subn", "split", "findall",
     "compile", "purge", "template", "escape", "A", "I", "L", "M", "S", "X",
     "U", "ASCII", "IGNORECASE", "LOCALE", "MULTILINE", "DOTALL", "VERBOSE",
     "UNICODE", "error" ]
@@ -154,6 +155,11 @@
     a match object, or None if no match was found."""
     return _compile(pattern, flags).match(string)
 
+def fullmatch(pattern, string, flags=0):
+    """Try to apply the pattern to all of the string, returning
+    a match object, or None if no match was found."""
+    return _compile(pattern, flags).fullmatch(string)
+
 def search(pattern, string, flags=0):
     """Scan through string looking for a match to the pattern, returning
     a match object, or None if no match was found."""
diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py
index 1ef68b8..a4e11c6 100644
--- a/Lib/test/test_re.py
+++ b/Lib/test/test_re.py
@@ -349,6 +349,36 @@
                          (None, 'b', None))
         self.assertEqual(pat.match('ac').group(1, 'b2', 3), ('a', None, 'c'))
 
+    def test_re_fullmatch(self):
+        # Issue 16203: Proposal: add re.fullmatch() method.
+        self.assertEqual(re.fullmatch(r"a", "a").span(), (0, 1))
+        for string in "ab", S("ab"):
+            self.assertEqual(re.fullmatch(r"a|ab", string).span(), (0, 2))
+        for string in b"ab", B(b"ab"), bytearray(b"ab"), memoryview(b"ab"):
+            self.assertEqual(re.fullmatch(br"a|ab", string).span(), (0, 2))
+        for a, b in "\xe0\xdf", "\u0430\u0431", "\U0001d49c\U0001d49e":
+            r = r"%s|%s" % (a, a + b)
+            self.assertEqual(re.fullmatch(r, a + b).span(), (0, 2))
+        self.assertEqual(re.fullmatch(r".*?$", "abc").span(), (0, 3))
+        self.assertEqual(re.fullmatch(r".*?", "abc").span(), (0, 3))
+        self.assertEqual(re.fullmatch(r"a.*?b", "ab").span(), (0, 2))
+        self.assertEqual(re.fullmatch(r"a.*?b", "abb").span(), (0, 3))
+        self.assertEqual(re.fullmatch(r"a.*?b", "axxb").span(), (0, 4))
+        self.assertIsNone(re.fullmatch(r"a+", "ab"))
+        self.assertIsNone(re.fullmatch(r"abc$", "abc\n"))
+        self.assertIsNone(re.fullmatch(r"abc\Z", "abc\n"))
+        self.assertIsNone(re.fullmatch(r"(?m)abc$", "abc\n"))
+        self.assertEqual(re.fullmatch(r"ab(?=c)cd", "abcd").span(), (0, 4))
+        self.assertEqual(re.fullmatch(r"ab(?<=b)cd", "abcd").span(), (0, 4))
+        self.assertEqual(re.fullmatch(r"(?=a|ab)ab", "ab").span(), (0, 2))
+
+        self.assertEqual(
+            re.compile(r"bc").fullmatch("abcd", pos=1, endpos=3).span(), (1, 3))
+        self.assertEqual(
+            re.compile(r".*?$").fullmatch("abcd", pos=1, endpos=3).span(), (1, 3))
+        self.assertEqual(
+            re.compile(r".*?").fullmatch("abcd", pos=1, endpos=3).span(), (1, 3))
+
     def test_re_groupref_exists(self):
         self.assertEqual(re.match('^(\()?([^()]+)(?(1)\))$', '(a)').groups(),
                          ('(', 'a'))