diff -r 9b053d3c74dc Lib/test/test_re.py --- a/Lib/test/test_re.py Wed Nov 16 15:56:50 2016 +0200 +++ b/Lib/test/test_re.py Fri Nov 18 08:53:48 2016 +0100 @@ -3,12 +3,13 @@ from test.support import verbose, run_un import io import locale import re -from re import Scanner import sre_compile +import string import sys -import string import traceback import unittest +import warnings +from re import Scanner from weakref import proxy # Misc tests from Tim Peters' re.doc @@ -1777,6 +1778,46 @@ SUBPATTERN None 0 0 self.assertIn('ASCII', str(re.A)) self.assertIn('DOTALL', str(re.S)) + def test_pattern_compare(self): + pattern1 = re.compile('abc', re.IGNORECASE) + + # equal + re.purge() + pattern2 = re.compile('abc', re.IGNORECASE) + self.assertEqual(hash(pattern2), hash(pattern1)) + self.assertEqual(pattern2, pattern1) + + # not equal: different pattern (comparison is case sensitive) + re.purge() + pattern3 = re.compile('ABC', re.IGNORECASE) + self.assertNotEqual(hash(pattern3), hash(pattern1)) + self.assertNotEqual(pattern3, pattern1) + + # not equal: different flag + re.purge() + pattern4 = re.compile('abc', re.ASCII) + self.assertNotEqual(hash(pattern4), hash(pattern1)) + self.assertNotEqual(pattern4, pattern1) + + # not equal: pattern of a different types (str vs bytes), + # comparison must not raise a BytesWarning + re.purge() + pattern5 = re.compile(b'abc', re.IGNORECASE) + with warnings.catch_warnings(): + warnings.simplefilter('error', BytesWarning) + self.assertNotEqual(hash(pattern5), hash(pattern1)) + self.assertNotEqual(pattern5, pattern1) + + # equal: test bytes patterns + re.purge() + pattern6 = re.compile(b'abc', re.IGNORECASE) + self.assertEqual(hash(pattern6), hash(pattern5)) + self.assertEqual(pattern6, pattern5) + + # only == and != comparison operators are supported + with self.assertRaises(TypeError): + pattern1 < pattern2 + class PatternReprTests(unittest.TestCase): def check(self, pattern, expected): diff -r 9b053d3c74dc Modules/_sre.c --- a/Modules/_sre.c Wed Nov 16 15:56:50 2016 +0200 +++ b/Modules/_sre.c Fri Nov 18 08:53:48 2016 +0100 @@ -2649,6 +2649,52 @@ pattern_scanner(PatternObject *self, PyO return (PyObject*) scanner; } +static Py_hash_t +pattern_hash(PatternObject *self) +{ + Py_hash_t hash; + + hash = PyObject_Hash(self->pattern); + if (hash == -1) { + return -1; + } + hash ^= self->isbytes; + hash ^= (self->flags << 1); + if (hash == -1) { + hash = -2; + } + return hash; +} + +static PyObject* +pattern_richcompare(PyObject *lefto, PyObject *righto, int op) +{ + PatternObject *left, *right; + int cmp; + + if (op != Py_EQ && op != Py_NE) { + Py_RETURN_NOTIMPLEMENTED; + } + + if (Py_TYPE(lefto) != &Pattern_Type || Py_TYPE(righto) != &Pattern_Type) { + Py_RETURN_NOTIMPLEMENTED; + } + left = (PatternObject *)lefto; + right = (PatternObject *)righto; + + cmp = (left->flags == right->flags && left->isbytes == right->isbytes); + if (cmp) { + cmp = PyObject_RichCompareBool(left->pattern, right->pattern, Py_EQ); + if (cmp < 0) { + return NULL; + } + } + if (op == Py_NE) { + cmp = !cmp; + } + return PyBool_FromLong(cmp); +} + #include "clinic/_sre.c.h" static PyMethodDef pattern_methods[] = { @@ -2693,7 +2739,7 @@ static PyTypeObject Pattern_Type = { 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ - 0, /* tp_hash */ + (hashfunc)pattern_hash, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ @@ -2703,7 +2749,7 @@ static PyTypeObject Pattern_Type = { pattern_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ - 0, /* tp_richcompare */ + pattern_richcompare, /* tp_richcompare */ offsetof(PatternObject, weakreflist), /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */