Failure to insert instantiated class of estimator in Pipeline produces an unclear error message

### Describe the bug

While building a pipeline I forgot the parenthesis during a step creation in the pipeline. I'm really not proud to admit that it took me a while to realize the mistake that I've made. I thought that maybe the error message in this instance could be improved if you guys agree. 

I feel like it's a pretty easy non-breaking fix this one.

Cheers!

### Steps/Code to Reproduce

```python
# Made a silly error while building a pipeline and
# thought that the error message could be improved.

from sklearn.decomposition import PCA
from sklearn.pipeline import Pipeline
import numpy as np

# Dummy data to showcase issue
X = np.ndarray(
    shape=(10, 5),
    buffer=np.random.rand(50),
    dtype=float,
)

pipeline = Pipeline(steps=[
    # ('step1', step1),
    # ('step2', step2),
    # ...
    ('pca', PCA), # <-- Error: PCA is not instantiated, currently it's an instance of abc.ABCMeta
])

# This will raise an error but the error message will be a bit unclear
X_transformed = pipeline.fit_transform(X) 
```

### Expected Results

I would expect an error that told me that a step was not instantiated correctly, or is of type `abc.ABCMeta`, should be instance of `sklearn.decomposition.PCA`.

### Actual Results

```
Traceback (most recent call last):
  File "test.py", line 23, in <module>
    X_trainsformed = pipeline.fit_transform(X)
  File "/opt/homebrew/Caskroom/miniconda/base/envs/dsml25/lib/python3.8/site-packages/sklearn/base.py", line 1152, in wrapper
    return fit_method(estimator, *args, **kwargs)
  File "/opt/homebrew/Caskroom/miniconda/base/envs/dsml25/lib/python3.8/site-packages/sklearn/pipeline.py", line 479, in fit_transform
    return last_step.fit_transform(Xt, y, **fit_params_last_step)
  File "/opt/homebrew/Caskroom/miniconda/base/envs/dsml25/lib/python3.8/site-packages/sklearn/utils/_set_output.py", line 157, in wrapped
    data_to_wrap = f(self, X, *args, **kwargs)
  File "/opt/homebrew/Caskroom/miniconda/base/envs/dsml25/lib/python3.8/site-packages/sklearn/base.py", line 1145, in wrapper
    estimator._validate_params()
AttributeError: 'numpy.ndarray' object has no attribute '_validate_params'
```

### Versions

```shell
System:
    python: 3.8.20 | packaged by conda-forge | (default, Sep 30 2024, 17:48:42)  [Clang 17.0.6 ]
executable: /opt/homebrew/Caskroom/miniconda/base/envs/dsml25/bin/python3
   machine: macOS-26.1-arm64-arm-64bit

Python dependencies:
      sklearn: 1.3.2
          pip: 24.3.1
   setuptools: 75.3.0
        numpy: 1.24.4
        scipy: 1.10.1
       Cython: None
       pandas: 2.0.3
   matplotlib: 3.7.3
       joblib: 1.4.2
threadpoolctl: 3.5.0

Built with OpenMP: True

threadpoolctl info:
       user_api: blas
   internal_api: openblas
    num_threads: 10
         prefix: libopenblas
       filepath: /opt/homebrew/Caskroom/miniconda/base/envs/dsml25/lib/libopenblas.0.dylib
        version: 0.3.25
threading_layer: openmp
   architecture: VORTEX

       user_api: openmp
   internal_api: openmp
    num_threads: 10
         prefix: libomp
       filepath: /opt/homebrew/Caskroom/miniconda/base/envs/dsml25/lib/libomp.dylib
        version: None
```

COMMENT:
I agree that, in the case of `PCA.fit(X)`, we shouldn't do anything, because I don't see an easy way without adapting every class separately, and, as @adrinjalali pointed out, this is basic Python behavior.

In the case of meta-estimators, I think I did find a simple solution. Namely, we already have a function that validates the names of the steps (`_validate_names`), which could be easily extended to also check if the corresponding estimator is a type and raise a more helpful error message. 
I'll open a PR tomorrow.

Side note:

While I was at it, I noticed that there is a definition of `_validate_estimators()` in `_BaseHeterogeneousEnsemble` in sklearn/ensemble/_base.py, that would also fit the purpose, but it is not used very consistently (contrary to `_validate_names`).
It is used in `_BaseVoting`'s definition of `fit` (sklearn/ensemble/_voting.py l. 78) 
and in `_BaseStacking`'s definition of `fit` (sklearn/ensemble/_stacking.py l. 185) 
and overwritten in `StackingClassifier` in _stacking.py, but `Pipeline`, and possibly the others mentioned above, don't use it but define their own estimator validation. 

I'm still a bit lost in the class hierarchies, but I guess a possible (low prio) follow-up issue could be to refactor the use of estimator validation in meta-estimators in general.

Labels: Bug