[Bug] `sklearn.base.BaseEstimator` subclasses not decoratable

### Describe the bug

Greetings, wondrous `sklearn` maintainers. This is @leycec, the maintainer of @beartype – a third-generation hybrid static-runtime type-checker with far too many hyphens in its description. Who even knows what that means at this point. The point is that I recently [fielded an issue](https://github.com/beartype/beartype/issues/340) from [one of my favourite users](https://github.com/tvdboom) requesting support for `sklearn` "metadata routing." I don't even know what that is... *but it sounds hot.*

Sadly, @beartype cannot provide this support – because the `sklearn.base.BaseEstimator` subclass fails to support decoration at a core level. That's outside our scope of control. But first, the minimal reproducible example (MRE) exhibiting this issue:

### Steps/Code to Reproduce

```python
import sklearn
from sklearn.base import BaseEstimator

sklearn.set_config(enable_metadata_routing=True)

class A(BaseEstimator):
    def fit(self, X, y, sample_weight=None):
        return self

A.set_fit_request = A.set_fit_request
a = A().set_fit_request(sample_weight=True)
```

### Expected Results

No exception is raised.

### Actual Results

```python
Traceback (most recent call last):
  File "/home/leycec/tmp/mopy.py", line 13, in <module>
    a = A().set_fit_request(sample_weight=True)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: RequestMethod.__get__.<locals>.func() takes 0 positional arguments but 1 was given
```

### Versions

```shell
System:
    python: 3.12.2 (main, Mar  9 2024, 18:58:45) [GCC 13.2.1 20240113]
executable: /usr/bin/python3.12
   machine: Linux-6.1.67-gentoo-x86_64-AMD_Athlon-tm-_II_X2_240_Processor-with-glibc2.38

Python dependencies:
      sklearn: 1.3.2
          pip: 24.0
   setuptools: 69.1.1
        numpy: 1.26.4
        scipy: 1.12.0
       Cython: 3.0.8
       pandas: 2.2.0
   matplotlib: 3.8.3
       joblib: 1.3.2
threadpoolctl: 3.3.0

Built with OpenMP: True

threadpoolctl info:
       user_api: blas
   internal_api: openblas
    num_threads: 6
         prefix: libopenblas
       filepath: /usr/lib64/libopenblas_barcelonap-r0.3.25.so
        version: 0.3.25
threading_layer: openmp
   architecture: BARCELONA

       user_api: openmp
   internal_api: openmp
    num_threads: 2
         prefix: libgomp
       filepath: /usr/lib/gcc/x86_64-pc-linux-gnu/13/libgomp.so.1.0.0
        version: None
```
```

COMMENT:
**Woah.** When tautologies are no longer tautological, you know you're in Python. I am Pikachu shocked face.

![](https://media1.tenor.com/m/CNI1fSM1XSoAAAAd/shocked-surprised.gif)

Surprisingly, this special madness gets a *little* saner when you move from non-data to data descriptors. Let's generalize the `Descriptor` class into a full-blown data descriptor. Throw in the `__set__()` dunder method! Can you guess what blows up next?

```python
class Descriptor:
    def __init__(self):
        self.value = 0

    def __get__(self, instance, owner):
        self.value += 1
        return self.value

    def __set__(self, instance, new_value):
        self.value = new_value
    
class A:
    x = Descriptor()

a = A()
print(a.x)
print(a.x)
a.x = a.x
print(a.x)
print(a.x)
```

The shocking answer is... *nothing*! Wierd, huh? I cleverly tricked everybody. Nothing blows up. In fact, that example actually behaves as expected by printing:

```
1
2
4
5
```

Nifty, huh? I actually didn't expect Python to destroy a non-data descriptor when you assign to that descriptor. I still think that's kinda uncool, but I also acknowledge that that's *probably* the best Python can do under the circumstances.

Still, it's *really* relieving that Python preserves data descriptors when you assign to them. This means that we can always avoid accidental destruction of non-data descriptors **by never defining a non-data descriptor in the first place.** If we stick to data descriptors by always defining a trivial `__set__()` dunder method (like above), then we can avoid all the Bad Stuff™ and undesirable behaviour associated with non-data descriptors. I think, anyway? Let's pretend I know what I'm talking about.

*Annnnnnyway.* This was such a fun deep dive into the unspeakable darkness of Python. Now let's go code stuff! :partying_face:

Labels: Bug