From 6ecc43d83801bd0337b771390ea5f4063689f275 Mon Sep 17 00:00:00 2001 From: Joost Kranendonk Date: Wed, 4 Jan 2017 16:23:41 +0100 Subject: [PATCH 01/14] BUG: Fix for .str.replace with repl function .str.replace now accepts a callable (function) as replacement string. It now raises a TypeError when repl is not string like nor a callable. Docstring updated accordingly. --- pandas/core/strings.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pandas/core/strings.py b/pandas/core/strings.py index 3041b17b99b17..fc8a5d74fe6b1 100644 --- a/pandas/core/strings.py +++ b/pandas/core/strings.py @@ -303,8 +303,9 @@ def str_replace(arr, pat, repl, n=-1, case=True, flags=0): ---------- pat : string Character sequence or regular expression - repl : string - Replacement sequence + repl : string or function + Replacement string or a function, which passed the match object and + must return a replacement string to be used. See :func:`re.sub`. n : int, default -1 (all) Number of replacements to make from start case : boolean, default True @@ -318,9 +319,9 @@ def str_replace(arr, pat, repl, n=-1, case=True, flags=0): """ # Check whether repl is valid (GH 13438) - if not is_string_like(repl): - raise TypeError("repl must be a string") - use_re = not case or len(pat) > 1 or flags + if not is_string_like(repl) or not callable(repl): + raise TypeError("repl must be a string or function") + use_re = not case or len(pat) > 1 or flags or callable(repl) if use_re: if not case: From 91c883d42dd7c37824a48a1e8cd444df27372a5d Mon Sep 17 00:00:00 2001 From: Joost Kranendonk Date: Wed, 4 Jan 2017 17:35:17 +0100 Subject: [PATCH 02/14] Update .str.replace docstring As suggested by @jreback --- pandas/core/strings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/core/strings.py b/pandas/core/strings.py index fc8a5d74fe6b1..4805b352c45a0 100644 --- a/pandas/core/strings.py +++ b/pandas/core/strings.py @@ -303,8 +303,8 @@ def str_replace(arr, pat, repl, n=-1, case=True, flags=0): ---------- pat : string Character sequence or regular expression - repl : string or function - Replacement string or a function, which passed the match object and + repl : string or callable + Replacement string or a callable, it's passed the match object and must return a replacement string to be used. See :func:`re.sub`. n : int, default -1 (all) Number of replacements to make from start From 4baa0a756c3a9a401c9f5509458afc5b69e82106 Mon Sep 17 00:00:00 2001 From: Joost Kranendonk Date: Wed, 4 Jan 2017 17:48:03 +0100 Subject: [PATCH 03/14] add tests for .str.replace with callable repl See #15055 and #15056 --- pandas/tests/test_strings.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pandas/tests/test_strings.py b/pandas/tests/test_strings.py index bbcd856250c51..b3fa43d1d9c9f 100644 --- a/pandas/tests/test_strings.py +++ b/pandas/tests/test_strings.py @@ -435,6 +435,12 @@ def test_replace(self): for data in (['a', 'b', None], ['a', 'b', 'c', 'ad']): values = klass(data) self.assertRaises(TypeError, values.str.replace, 'a', repl) + + # GH 15055, callable repl + repl = lambda m: m.group(0).swapcase() + result = values.str.replace('[a-z][A-Z]{2}', repl, n=2) + exp = Series([u('foObaD__baRbaD'), NA]) + tm.assert_series_equal(result, exp) def test_repeat(self): values = Series(['a', 'b', NA, 'c', NA, 'd']) From 30d4727a530b4547aeda39180af9d64333e1ad2e Mon Sep 17 00:00:00 2001 From: Joost Kranendonk Date: Thu, 5 Jan 2017 10:55:38 +0100 Subject: [PATCH 04/14] Bug fix in .str.replace type checking done wrong Dull mistake --- pandas/core/strings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/strings.py b/pandas/core/strings.py index 4805b352c45a0..777e92d7ba591 100644 --- a/pandas/core/strings.py +++ b/pandas/core/strings.py @@ -319,7 +319,7 @@ def str_replace(arr, pat, repl, n=-1, case=True, flags=0): """ # Check whether repl is valid (GH 13438) - if not is_string_like(repl) or not callable(repl): + if not (is_string_like(repl) or callable(repl)): raise TypeError("repl must be a string or function") use_re = not case or len(pat) > 1 or flags or callable(repl) From 067a7a8c01ca08cb9a83cdda18cbb540c745e462 Mon Sep 17 00:00:00 2001 From: Joost Kranendonk Date: Fri, 6 Jan 2017 09:20:07 +0100 Subject: [PATCH 05/14] Fix testing bug for .str.replace I copied a previous test as a draft, assumed that I could reuse `values` but did not mention it was redefined in the unicode and GH 13438 tests. Now corrected. Just redefine `values`. I could have put my test before the unicode tests but this keeps the order. --- pandas/tests/test_strings.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pandas/tests/test_strings.py b/pandas/tests/test_strings.py index b3fa43d1d9c9f..67164accc6ff4 100644 --- a/pandas/tests/test_strings.py +++ b/pandas/tests/test_strings.py @@ -436,10 +436,19 @@ def test_replace(self): values = klass(data) self.assertRaises(TypeError, values.str.replace, 'a', repl) - # GH 15055, callable repl + ## GH 15055, callable repl + values = Series(['fooBAD__barBAD', NA]) + + # test with callable + repl = lambda m: m.group(0).swapcase() + result = values.str.replace('[a-z][A-Z]{2}', repl, n=2) + exp = Series(['foObaD__baRbaD', NA]) + tm.assert_series_equal(result, exp) + + # test with wrong type repl = lambda m: m.group(0).swapcase() result = values.str.replace('[a-z][A-Z]{2}', repl, n=2) - exp = Series([u('foObaD__baRbaD'), NA]) + exp = Series(['foObaD__baRbaD', NA]) tm.assert_series_equal(result, exp) def test_repeat(self): From ae04a3e37cb11713e61f546395aa8cf39528123b Mon Sep 17 00:00:00 2001 From: Joost Kranendonk Date: Fri, 6 Jan 2017 16:08:04 +0100 Subject: [PATCH 06/14] Add whatsnew for .str.replace with callable repl See #15056 --- doc/source/whatsnew/v0.20.0.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v0.20.0.txt b/doc/source/whatsnew/v0.20.0.txt index 2db03724e564d..be5dd9c5ba05d 100644 --- a/doc/source/whatsnew/v0.20.0.txt +++ b/doc/source/whatsnew/v0.20.0.txt @@ -23,6 +23,7 @@ New features ~~~~~~~~~~~~ - Integration with the ``feather-format``, including a new top-level ``pd.read_feather()`` and ``DataFrame.to_feather()`` method, see :ref:`here `. +- ``.str.replace`` now accepts a callable replacement which is passed to ``re.sub`` (:issue:`15055`) From 27065a29ba883f79499e4978872611f23d606f7e Mon Sep 17 00:00:00 2001 From: Joost Kranendonk Date: Fri, 6 Jan 2017 17:11:55 +0100 Subject: [PATCH 07/14] Reraise TypeError only with wrong number of args This influences all StringMethods were a used passes a callable to a StringMethod, e.g. `.str.replace`, that expects a wrong number of arguments. See #15056 --- pandas/core/strings.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pandas/core/strings.py b/pandas/core/strings.py index 777e92d7ba591..669b9b4113ca4 100644 --- a/pandas/core/strings.py +++ b/pandas/core/strings.py @@ -167,7 +167,14 @@ def _map(f, arr, na_mask=False, na_value=np.nan, dtype=object): try: convert = not all(mask) result = lib.map_infer_mask(arr, f, mask.view(np.uint8), convert) - except (TypeError, AttributeError): + except (TypeError, AttributeError) as e: + re_missing = (r'missing \d+ required (positional|keyword-only) ' + 'arguments?') + re_takes = (r'takes (from)?\d+ (to \d+)?positional arguments? ' + 'but \d+ (was|were) given') + if len(e.args) >= 1 and (re.search(re_missing, e.args[0]) + or re.search(re_takes, e.args[0])): + raise e def g(x): try: From 14beb2122bcd476fd1e8d8207b184cbe5cb0d640 Mon Sep 17 00:00:00 2001 From: Joost Kranendonk Date: Fri, 6 Jan 2017 17:24:52 +0100 Subject: [PATCH 08/14] Add test for .str.replace with regex named groups --- pandas/tests/test_strings.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/pandas/tests/test_strings.py b/pandas/tests/test_strings.py index 67164accc6ff4..3ee94db15e330 100644 --- a/pandas/tests/test_strings.py +++ b/pandas/tests/test_strings.py @@ -436,7 +436,7 @@ def test_replace(self): values = klass(data) self.assertRaises(TypeError, values.str.replace, 'a', repl) - ## GH 15055, callable repl + ## GH 15055 values = Series(['fooBAD__barBAD', NA]) # test with callable @@ -445,10 +445,20 @@ def test_replace(self): exp = Series(['foObaD__baRbaD', NA]) tm.assert_series_equal(result, exp) - # test with wrong type - repl = lambda m: m.group(0).swapcase() - result = values.str.replace('[a-z][A-Z]{2}', repl, n=2) - exp = Series(['foObaD__baRbaD', NA]) + # test with wrong number of arguments + repl = lambda m, bad: None + re_msg = "^\(\) missing 1 required positional argument: 'bad'$" + self.assertRaisesRegex(TypeError, re_msg, values.str.replace, 'a', repl) + + repl = lambda: None + re_msg = '^\(\) takes 0 positional arguments but 1 was given$' + self.assertRaisesRegex(TypeError, re_msg, values.str.replace, 'a', repl) + + # test regex named groups + values = Series(['Foo Bar Baz', NA]) + repl = lambda m: m.group('middle').swapcase() + result = values.str.replace(r"(?P\w+) (?P\w+) (?P\w+)", repl) + exp = Series(['bAR', NA]) tm.assert_series_equal(result, exp) def test_repeat(self): From f15ee2a00db36c2c7cbbfaef45ea71bde67513df Mon Sep 17 00:00:00 2001 From: Joost Kranendonk Date: Sun, 22 Jan 2017 16:35:10 +0100 Subject: [PATCH 09/14] improve test .str.replace with callable - separate test function - fix missing assertRaisesRegex(p) --- pandas/tests/test_strings.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pandas/tests/test_strings.py b/pandas/tests/test_strings.py index 3ee94db15e330..f8ccf99ca0f6c 100644 --- a/pandas/tests/test_strings.py +++ b/pandas/tests/test_strings.py @@ -436,6 +436,7 @@ def test_replace(self): values = klass(data) self.assertRaises(TypeError, values.str.replace, 'a', repl) + def test_replace_callable(self): ## GH 15055 values = Series(['fooBAD__barBAD', NA]) @@ -448,16 +449,19 @@ def test_replace(self): # test with wrong number of arguments repl = lambda m, bad: None re_msg = "^\(\) missing 1 required positional argument: 'bad'$" - self.assertRaisesRegex(TypeError, re_msg, values.str.replace, 'a', repl) + with tm.assertRaisesRegexp(TypeError, re_msg): + values.str.replace('a', repl) repl = lambda: None re_msg = '^\(\) takes 0 positional arguments but 1 was given$' - self.assertRaisesRegex(TypeError, re_msg, values.str.replace, 'a', repl) + with tm.assertRaisesRegexp(TypeError, re_msg): + values.str.replace('a', repl) # test regex named groups values = Series(['Foo Bar Baz', NA]) + pat = r"(?P\w+) (?P\w+) (?P\w+)" repl = lambda m: m.group('middle').swapcase() - result = values.str.replace(r"(?P\w+) (?P\w+) (?P\w+)", repl) + result = values.str.replace(pat, repl) exp = Series(['bAR', NA]) tm.assert_series_equal(result, exp) From 40c0d976f1dbe66fe64c95c7439d8b2942eae25e Mon Sep 17 00:00:00 2001 From: Joost Kranendonk Date: Mon, 23 Jan 2017 10:01:52 +0100 Subject: [PATCH 10/14] improve .str.replace with callable - add inline comments - add examples with string and callable in __doc__ and text.rst doc - extend method summary Series.str.replace in text.rst doc --- doc/source/text.rst | 21 +++++++++++++++- pandas/core/strings.py | 55 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/doc/source/text.rst b/doc/source/text.rst index 3a4a57ff4da95..52e05c5d511bc 100644 --- a/doc/source/text.rst +++ b/doc/source/text.rst @@ -146,6 +146,25 @@ following code will cause trouble because of the regular expression meaning of # We need to escape the special character (for >1 len patterns) dollars.str.replace(r'-\$', '-') +The ``replace`` method can also take a callable as replacement. It is called +on every ``pat`` using :func:`re.sub`. The callable should expect one +positional argument (a regex object) and return a string. + +.. versionadded:: 0.20.0 + +.. ipython:: python + + # Reverse every lowercase alphabetic word + pat = r'[a-z]+' + repl = lambda m: m.group(0)[::-1] + pd.Series(['foo 123', 'bar baz', np.nan]).str.replace(pat, repl) + + # Using regex groups + pat = r"(?P\w+) (?P\w+) (?P\w+)" + repl = lambda m: m.group('two').swapcase() + pd.Series(['Foo Bar Baz', np.nan]).str.replace(pat, repl) + + Indexing with ``.str`` ---------------------- @@ -406,7 +425,7 @@ Method Summary :meth:`~Series.str.join`;Join strings in each element of the Series with passed separator :meth:`~Series.str.get_dummies`;Split strings on the delimiter returning DataFrame of dummy variables :meth:`~Series.str.contains`;Return boolean array if each string contains pattern/regex - :meth:`~Series.str.replace`;Replace occurrences of pattern/regex with some other string + :meth:`~Series.str.replace`;Replace occurrences of pattern/regex with some other string or the return value of a callable given the occurrence :meth:`~Series.str.repeat`;Duplicate values (``s.str.repeat(3)`` equivalent to ``x * 3``) :meth:`~Series.str.pad`;"Add whitespace to left, right, or both sides of strings" :meth:`~Series.str.center`;Equivalent to ``str.center`` diff --git a/pandas/core/strings.py b/pandas/core/strings.py index 669b9b4113ca4..a89179c62279f 100644 --- a/pandas/core/strings.py +++ b/pandas/core/strings.py @@ -168,6 +168,8 @@ def _map(f, arr, na_mask=False, na_value=np.nan, dtype=object): convert = not all(mask) result = lib.map_infer_mask(arr, f, mask.view(np.uint8), convert) except (TypeError, AttributeError) as e: + # Reraise the exception if callable `f` got wrong number of args. + # The user may want to be warned by this, instead of getting NaN re_missing = (r'missing \d+ required (positional|keyword-only) ' 'arguments?') re_takes = (r'takes (from)?\d+ (to \d+)?positional arguments? ' @@ -311,8 +313,12 @@ def str_replace(arr, pat, repl, n=-1, case=True, flags=0): pat : string Character sequence or regular expression repl : string or callable - Replacement string or a callable, it's passed the match object and - must return a replacement string to be used. See :func:`re.sub`. + Replacement string or a callable. The callable is passed the regex + match object and must return a replacement string to be used. + See :func:`re.sub`. + + .. versionadded:: 0.20.0 + n : int, default -1 (all) Number of replacements to make from start case : boolean, default True @@ -323,11 +329,52 @@ def str_replace(arr, pat, repl, n=-1, case=True, flags=0): Returns ------- replaced : Series/Index of objects + + Examples + -------- + When ``repl`` is a string, every ``pat`` is replaced as with + :meth:`str.replace`. NaN value(s) in the Series are left as is. + + >>> Series(['foo', 'fuz', np.nan]).str.replace('f', 'b') + 0 boo + 1 buz + 2 NaN + dtype: object + + When ``repl`` is a callable, it is called on every ``pat`` using + :func:`re.sub`. The callable should expect one positional argument + (a regex object) and return a string. + + To get the idea: + + >>> Series(['foo', 'fuz', np.nan]).str.replace('f', repr) + 0 <_sre.SRE_Match object; span=(0, 1), match='f'>oo + 1 <_sre.SRE_Match object; span=(0, 1), match='f'>uz + 2 NaN + dtype: object + + Reverse every lowercase alphabetic word: + + >>> repl = lambda m: m.group(0)[::-1] + >>> Series(['foo 123', 'bar baz', np.nan]).str.replace(r'[a-z]+', repl) + 0 oof 123 + 1 rab zab + 2 NaN + dtype: object + + Using regex groups: + + >>> pat = r"(?P\w+) (?P\w+) (?P\w+)" + >>> repl = lambda m: m.group('two').swapcase() + >>> Series(['Foo Bar Baz', np.nan]).str.replace(pat, repl) + 0 bAR + 1 NaN + dtype: object """ - # Check whether repl is valid (GH 13438) + # Check whether repl is valid (GH 13438, GH 15055) if not (is_string_like(repl) or callable(repl)): - raise TypeError("repl must be a string or function") + raise TypeError("repl must be a string or callable") use_re = not case or len(pat) > 1 or flags or callable(repl) if use_re: From e15dcdf544f02de04573b942132186d8ea397676 Mon Sep 17 00:00:00 2001 From: Joost Kranendonk Date: Mon, 23 Jan 2017 15:31:55 +0100 Subject: [PATCH 11/14] fix bug catch TypeError with wrong number of args The regex filtering TypeErrors with argument number problems is now unified and suitable for Python 2.7 --- pandas/core/strings.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pandas/core/strings.py b/pandas/core/strings.py index a89179c62279f..4656fef269aea 100644 --- a/pandas/core/strings.py +++ b/pandas/core/strings.py @@ -170,12 +170,9 @@ def _map(f, arr, na_mask=False, na_value=np.nan, dtype=object): except (TypeError, AttributeError) as e: # Reraise the exception if callable `f` got wrong number of args. # The user may want to be warned by this, instead of getting NaN - re_missing = (r'missing \d+ required (positional|keyword-only) ' - 'arguments?') - re_takes = (r'takes (from)?\d+ (to \d+)?positional arguments? ' - 'but \d+ (was|were) given') - if len(e.args) >= 1 and (re.search(re_missing, e.args[0]) - or re.search(re_takes, e.args[0])): + re_error = (r'(takes|(missing)) (no|(exactly )?\d+) ' + r'(?(2)required )(positional )?arguments?') + if len(e.args) >= 1 and re.search(re_error, e.args[0]): raise e def g(x): From c2cc13a496e9e3f8c76a8004adebb95e44486170 Mon Sep 17 00:00:00 2001 From: Joost Kranendonk Date: Mon, 23 Jan 2017 15:35:52 +0100 Subject: [PATCH 12/14] Update v0.20.0.txt --- doc/source/whatsnew/v0.20.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.20.0.txt b/doc/source/whatsnew/v0.20.0.txt index be5dd9c5ba05d..25dffc9a4960d 100644 --- a/doc/source/whatsnew/v0.20.0.txt +++ b/doc/source/whatsnew/v0.20.0.txt @@ -23,7 +23,7 @@ New features ~~~~~~~~~~~~ - Integration with the ``feather-format``, including a new top-level ``pd.read_feather()`` and ``DataFrame.to_feather()`` method, see :ref:`here `. -- ``.str.replace`` now accepts a callable replacement which is passed to ``re.sub`` (:issue:`15055`) +- ``.str.replace`` now accepts a callable, as replacement, which is passed to ``re.sub`` (:issue:`15055`) From 90779ce4033b134fd8a3c3d26677ffda4c71bb50 Mon Sep 17 00:00:00 2001 From: Joost Kranendonk Date: Mon, 23 Jan 2017 15:54:59 +0100 Subject: [PATCH 13/14] fix linting issues - trailing whitespace - whitespace empty line - too many '#' in comment --- pandas/core/strings.py | 10 +++++----- pandas/tests/test_strings.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pandas/core/strings.py b/pandas/core/strings.py index 4656fef269aea..1b3a79a55192c 100644 --- a/pandas/core/strings.py +++ b/pandas/core/strings.py @@ -310,8 +310,8 @@ def str_replace(arr, pat, repl, n=-1, case=True, flags=0): pat : string Character sequence or regular expression repl : string or callable - Replacement string or a callable. The callable is passed the regex - match object and must return a replacement string to be used. + Replacement string or a callable. The callable is passed the regex + match object and must return a replacement string to be used. See :func:`re.sub`. .. versionadded:: 0.20.0 @@ -329,7 +329,7 @@ def str_replace(arr, pat, repl, n=-1, case=True, flags=0): Examples -------- - When ``repl`` is a string, every ``pat`` is replaced as with + When ``repl`` is a string, every ``pat`` is replaced as with :meth:`str.replace`. NaN value(s) in the Series are left as is. >>> Series(['foo', 'fuz', np.nan]).str.replace('f', 'b') @@ -338,8 +338,8 @@ def str_replace(arr, pat, repl, n=-1, case=True, flags=0): 2 NaN dtype: object - When ``repl`` is a callable, it is called on every ``pat`` using - :func:`re.sub`. The callable should expect one positional argument + When ``repl`` is a callable, it is called on every ``pat`` using + :func:`re.sub`. The callable should expect one positional argument (a regex object) and return a string. To get the idea: diff --git a/pandas/tests/test_strings.py b/pandas/tests/test_strings.py index f8ccf99ca0f6c..ca21b8185973e 100644 --- a/pandas/tests/test_strings.py +++ b/pandas/tests/test_strings.py @@ -435,9 +435,9 @@ def test_replace(self): for data in (['a', 'b', None], ['a', 'b', 'c', 'ad']): values = klass(data) self.assertRaises(TypeError, values.str.replace, 'a', repl) - + def test_replace_callable(self): - ## GH 15055 + # GH 15055 values = Series(['fooBAD__barBAD', NA]) # test with callable From 826730c55dfea300b35bcfd4b978e6481f44c48b Mon Sep 17 00:00:00 2001 From: Joost Kranendonk Date: Mon, 23 Jan 2017 18:15:35 +0100 Subject: [PATCH 14/14] simplify .str.replace TypeError reraising and test --- pandas/core/strings.py | 10 +++++++--- pandas/tests/test_strings.py | 22 +++++++++++++++------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/pandas/core/strings.py b/pandas/core/strings.py index 1b3a79a55192c..c48defe39a011 100644 --- a/pandas/core/strings.py +++ b/pandas/core/strings.py @@ -170,9 +170,13 @@ def _map(f, arr, na_mask=False, na_value=np.nan, dtype=object): except (TypeError, AttributeError) as e: # Reraise the exception if callable `f` got wrong number of args. # The user may want to be warned by this, instead of getting NaN - re_error = (r'(takes|(missing)) (no|(exactly )?\d+) ' - r'(?(2)required )(positional )?arguments?') - if len(e.args) >= 1 and re.search(re_error, e.args[0]): + if compat.PY2: + p_err = r'takes (no|(exactly|at (least|most)) ?\d+) arguments?' + else: + p_err = (r'((takes)|(missing)) (?(2)from \d+ to )?\d+ ' + r'(?(3)required )positional arguments?') + + if len(e.args) >= 1 and re.search(p_err, e.args[0]): raise e def g(x): diff --git a/pandas/tests/test_strings.py b/pandas/tests/test_strings.py index ca21b8185973e..47b64eac33d0b 100644 --- a/pandas/tests/test_strings.py +++ b/pandas/tests/test_strings.py @@ -446,15 +446,23 @@ def test_replace_callable(self): exp = Series(['foObaD__baRbaD', NA]) tm.assert_series_equal(result, exp) - # test with wrong number of arguments - repl = lambda m, bad: None - re_msg = "^\(\) missing 1 required positional argument: 'bad'$" - with tm.assertRaisesRegexp(TypeError, re_msg): - values.str.replace('a', repl) + # test with wrong number of arguments, raising an error + if compat.PY2: + p_err = r'takes (no|(exactly|at (least|most)) ?\d+) arguments?' + else: + p_err = (r'((takes)|(missing)) (?(2)from \d+ to )?\d+ ' + r'(?(3)required )positional arguments?') repl = lambda: None - re_msg = '^\(\) takes 0 positional arguments but 1 was given$' - with tm.assertRaisesRegexp(TypeError, re_msg): + with tm.assertRaisesRegexp(TypeError, p_err): + values.str.replace('a', repl) + + repl = lambda m, x: None + with tm.assertRaisesRegexp(TypeError, p_err): + values.str.replace('a', repl) + + repl = lambda m, x, y=None: None + with tm.assertRaisesRegexp(TypeError, p_err): values.str.replace('a', repl) # test regex named groups