Repository: pandas-dev/pandas

Issue:
DEPR: Deprecating Function Arguments
When we want to deprecate and remove arguments, our general methodology has been to issue the deprecation warning ONLY when a non-default argument has been passed in.

However, @jorisvandenbossche pointed out in #17820 that this warnings framework does not take into account situations where the user EXPLICITLY passes in the default argument.  Thus, we risk breaking downstream code in that case.

An alternative method would be to *change* the default argument (to some dummy) that we can use to catch this argument when passed in with the current accepted values.

While I have not seen this case emerge (patching is relatively painless, which might be why we haven't seen this issue on our tracker), I think it's worthwhile considering as we move forward with parameter deprecations + adjusting previous ones.


Discussion:
I disagree with not raising for the default value is how it is typically done. I think it is rather the other way around (we normally put the deprecated kwarg to a default of None, to be able to distinguish the default and an actual user passed value). This is also how the `deprecate_kwarg` decorator works. 

It might be that we just missed a few of those cases while reviewing in the PRs where you deprecated something, and so you have the impression that this is not the typical way deprecations are done (and likewise it is possible that I only reviewed mainly those PRs where we do it this way and thus I have the opposite impression)

Of course this issue is only relevant for a certain part of the deprecations (eg it is not relevant for deprecating/renaming kwargs that have no default value, or for changing a default value, or for deprecating/renaming a full method/class). 
Let me go through the relevant ones for 0.21 release:

- `read_excel` sheetname -> sheet_name kwarg rename, with a default of 0: this raises the warning when you pass `sheetname=0` (as it is how the `deprecate_kwarg` decorator works)
- `take` method `convert` kwarg: not raising for `convert=True` (IMO this should be fixed)
- `raise_on_errors` kwarg in `where` with default of True: raises for the default (if you look at the implementation, it actually does `if raise_on_error is not None: ... [raise warning]`)

It is true that it is making the signature less informative, but I think we need to have this warning, as it is the purpose of the warning that people can change their code *before* it breaks. 
deprecated kwargs should *always* be ``None``. Then in the code you can set them to the actual value; this allows detection and will show a warning if it was passed at all.

All of the examples by @jorisvandenbossche should be fixed.


Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -2172,6 +2172,7 @@ def _take(self, indices, axis=0, convert=True, is_copy=True):
             selecting rows, "1" means that we are selecting columns, etc.
         convert : bool, default True
             .. deprecated:: 0.21.0
+               In the future, negative indices will always be converted.
 
             Whether to convert negative indices into positive ones.
             For example, ``-1`` would map to the ``len(axis) - 1``.
@@ -2234,14 +2235,15 @@ class  max_speed
         """
 
     @Appender(_shared_docs['take'])
-    def take(self, indices, axis=0, convert=True, is_copy=True, **kwargs):
-        nv.validate_take(tuple(), kwargs)
-
-        if not convert:
+    def take(self, indices, axis=0, convert=None, is_copy=True, **kwargs):
+        if convert is not None:
             msg = ("The 'convert' parameter is deprecated "
                    "and will be removed in a future version.")
             warnings.warn(msg, FutureWarning, stacklevel=2)
+        else:
+            convert = True
 
+        convert = nv.validate_take(tuple(), kwargs)
         return self._take(indices, axis=axis, convert=convert, is_copy=is_copy)
 
     def xs(self, key, axis=0, level=None, drop_level=True):
diff --git a/pandas/core/sparse/series.py b/pandas/core/sparse/series.py
--- a/pandas/core/sparse/series.py
+++ b/pandas/core/sparse/series.py
@@ -387,7 +387,7 @@ def _ixs(self, i, axis=0):
         """
         label = self.index[i]
         if isinstance(label, Index):
-            return self.take(i, axis=axis, convert=True)
+            return self.take(i, axis=axis)
         else:
             return self._get_val_at(i)
 
@@ -629,14 +629,15 @@ def sparse_reindex(self, new_index):
                                  fill_value=self.fill_value).__finalize__(self)
 
     @Appender(generic._shared_docs['take'])
-    def take(self, indices, axis=0, convert=True, *args, **kwargs):
-        convert = nv.validate_take_with_convert(convert, args, kwargs)
-
-        if not convert:
+    def take(self, indices, axis=0, convert=None, *args, **kwargs):
+        if convert is not None:
             msg = ("The 'convert' parameter is deprecated "
                    "and will be removed in a future version.")
             warnings.warn(msg, FutureWarning, stacklevel=2)
+        else:
+            convert = True
 
+        nv.validate_take_with_convert(convert, args, kwargs)
         new_values = SparseArray.take(self.values, indices)
         new_index = self.index.take(indices)
         return self._constructor(new_values,


Test patch:
diff --git a/pandas/tests/frame/test_axis_select_reindex.py b/pandas/tests/frame/test_axis_select_reindex.py
--- a/pandas/tests/frame/test_axis_select_reindex.py
+++ b/pandas/tests/frame/test_axis_select_reindex.py
@@ -944,6 +944,10 @@ def test_take(self):
             expected = df.reindex(df.index.take(order))
             assert_frame_equal(result, expected)
 
+            with tm.assert_produces_warning(FutureWarning):
+                result = df.take(order, convert=True, axis=0)
+                assert_frame_equal(result, expected)
+
             with tm.assert_produces_warning(FutureWarning):
                 result = df.take(order, convert=False, axis=0)
                 assert_frame_equal(result, expected)
diff --git a/pandas/tests/sparse/test_series.py b/pandas/tests/sparse/test_series.py
--- a/pandas/tests/sparse/test_series.py
+++ b/pandas/tests/sparse/test_series.py
@@ -528,6 +528,9 @@ def _compare(idx):
         exp = pd.Series(np.repeat(nan, 5))
         tm.assert_series_equal(sp.take([0, 1, 2, 3, 4]), exp)
 
+        with tm.assert_produces_warning(FutureWarning):
+            sp.take([1, 5], convert=True)
+
         with tm.assert_produces_warning(FutureWarning):
             sp.take([1, 5], convert=False)
 
@@ -535,21 +538,17 @@ def test_numpy_take(self):
         sp = SparseSeries([1.0, 2.0, 3.0])
         indices = [1, 2]
 
-        # gh-17352: older versions of numpy don't properly
-        # pass in arguments to downstream .take() implementations.
-        warning = FutureWarning if _np_version_under1p12 else None
-
-        with tm.assert_produces_warning(warning, check_stacklevel=False):
+        if not _np_version_under1p12:
             tm.assert_series_equal(np.take(sp, indices, axis=0).to_dense(),
                                    np.take(sp.to_dense(), indices, axis=0))
 
-        msg = "the 'out' parameter is not supported"
-        tm.assert_raises_regex(ValueError, msg, np.take,
-                               sp, indices, out=np.empty(sp.shape))
+            msg = "the 'out' parameter is not supported"
+            tm.assert_raises_regex(ValueError, msg, np.take,
+                                   sp, indices, out=np.empty(sp.shape))
 
-        msg = "the 'mode' parameter is not supported"
-        tm.assert_raises_regex(ValueError, msg, np.take,
-                               sp, indices, mode='clip')
+            msg = "the 'mode' parameter is not supported"
+            tm.assert_raises_regex(ValueError, msg, np.take,
+                                   sp, indices, out=None, mode='clip')
 
     def test_setitem(self):
         self.bseries[5] = 7.