BUG Wrong error raised for OneVsRestClassifier with a sub-estimator that doesn't allow partial_fit

### Describe the bug

When using the `OneVsRestClassifier` with a sub-estimator, that doesn't have `partial_fit` implemented, a misleading error message is shown: `AttributeError: This 'OneVsRestClassifier' has no attribute 'partial_fit'`.

Though, `OneVsRestClassifier` does implement `partial_fit`, but the underlying estimator doesn't.

There is an appropriate error raising already implemented in
https://github.com/scikit-learn/scikit-learn/blob/f1e89363f6777155a25b3574db9f0fc5c21a8c51/sklearn/multiclass.py#L437

```python
            if not hasattr(self.estimator, "partial_fit"):
                raise ValueError(
                    ("Base estimator {0}, doesn't have partial_fit method").format(
                        self.estimator
                    )
                )
```
But it's not run, because the AttributeError from the `@available_if` decorator/descriptor thing pops up earlier.

I'd like to learn about that, repair that and add a test to make sure this doesn't happen again by accident.
I will also check if other methods from the multiclass classifiers are also affected.

Is it alright if I go ahead with this?

### Steps/Code to Reproduce

```python
from sklearn.datasets import load_iris
from sklearn.multiclass import OneVsRestClassifier
from sklearn.linear_model import LogisticRegression
import numpy as np

iris = load_iris()
sample_weight = np.ones_like(iris.target, dtype=np.float64)
clf = OneVsRestClassifier(
estimator=LogisticRegression(random_state=42)
)
clf.partial_fit(iris.data, iris.target)
```

### Expected Results

`ValueError:  LogisticRegression doesn't have partial_fit method`

### Actual Results

`AttributeError: This 'OneVsRestClassifier' has no attribute 'partial_fit'`.

### Versions

```shell
System:
    python: 3.10.6 (main, Oct 10 2022, 12:43:33) [GCC 9.4.0]
executable: /home/stefanie/.pyenv/versions/3.10.6/envs/scikit-learn_dev/bin/python
   machine: Linux-5.15.0-91-generic-x86_64-with-glibc2.31

Python dependencies:
      sklearn: 1.5.dev0
          pip: 23.3.2
   setuptools: 63.2.0
        numpy: 1.26.0
        scipy: 1.11.3
       Cython: 0.29.34
       pandas: 2.1.4
   matplotlib: 3.8.0
       joblib: 1.3.2
threadpoolctl: 3.1.0

Built with OpenMP: True

threadpoolctl info:
       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: /home/stefanie/.pyenv/versions/3.10.6/envs/scikit-learn_dev/lib/python3.10/site-packages/numpy.libs/libopenblas64_p-r0-0cf96a72.3.23.dev.so
        version: 0.3.23.dev
threading_layer: pthreads
   architecture: SkylakeX
    num_threads: 8

       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: /home/stefanie/.pyenv/versions/3.10.6/envs/scikit-learn_dev/lib/python3.10/site-packages/scipy.libs/libopenblasp-r0-23e5df77.3.21.dev.so
        version: 0.3.21.dev
threading_layer: pthreads
   architecture: SkylakeX
    num_threads: 8

       user_api: openmp
   internal_api: openmp
         prefix: libgomp
       filepath: /usr/lib/x86_64-linux-gnu/libgomp.so.1.0.0
        version: None
    num_threads: 8
```

COMMENT:
Your suggestion with the wrapper function seems to work out over several layers, @glemaitre:

```python
from sklearn.datasets import load_iris
from sklearn.multiclass import OneVsRestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.cluster import KMeans

iris = load_iris()
sample_weight = np.ones_like(iris.target, dtype=np.float64)

# 1st layer
pipe = Pipeline([("logreg", LogisticRegression())])
clf = OneVsRestClassifier(estimator=pipe).fit(iris.data, iris.target)
clf.transform(iris.data) #AttributeError: 'OneVsRestClassifier' object has no attribute 'transform'

# 2nd layer
pipe = Pipeline([("logreg", LogisticRegression())])
clf = OneVsRestClassifier(estimator=pipe).fit(iris.data, iris.target)
clf.partial_fit(iris.data) #AttributeError: 'Pipeline' object has no attribute 'partial_fit'

# 3rd layer
pipe = Pipeline([("kmeans", KMeans())])
clf = OneVsRestClassifier(estimator=pipe).fit(iris.data, iris.target)
clf.predict_proba(iris.data) # AttributeError: 'KMeans' object has no attribute 'predict_proba'
```
So, I would now look for more cases (searching for `return lambda self:` as suggested) and make a PR. 

I'd also really like to know @thomasjpfan's opinion on that, because I have the impression that the original intention of `available_if` was rather to have the meta-estimator display AttribributeError if their sub-estimators don't implement a method. 

However, I still find the default error message from `available_if` confusing in some cases and would be happy to have more custom ones (even more custom than the sub-estimator ones).

Labels: Bug