Repository: sktime/sktime

Issue:
[BUG] ForecastingPipeline constructor hangs
**Describe the bug**
My program hangs when constructing a `ForecastingPipeline`.

**To Reproduce**

```python
from sktime.forecasting.compose import make_reduction
from sktime.forecasting.compose import ForecastingPipeline
from sktime.forecasting.compose import TransformedTargetForecaster
from sktime.transformations.series.detrend import Detrender

import xgboost as xgb

transformer = TransformedTargetForecaster(
        [
            ("detrend", Detrender()),
            ("xgboost_forecaster", make_reduction(
                estimator=xgb.XGBRegressor(), 
                window_length=12,
                strategy="direct",
                )
            ),
        ]
    )
forecaster = ForecastingPipeline(transformer)
```
When running this interactively, the first statement returns immediately and the second statement hangs.

**Expected behavior**
the `ForecastingPipeline(..)` constructor returns almost immediately

**Additional context**
Mac M1

**Versions**
* Python 3.10.10
* sktime 0.16.1

output from show_versions:

```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/utils/_maint/_show_versions.py", line 85, in show_versions
    deps_info = _get_deps_info()
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/utils/_maint/_show_versions.py", line 73, in _get_deps_info
    mod = importlib.import_module(modname)
  File "/opt/homebrew/Cellar/python@3.10/3.10.10_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/setuptools/__init__.py", line 8, in <module>
    import _distutils_hack.override  # noqa: F401
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/_distutils_hack/override.py", line 1, in <module>
    __import__('_distutils_hack').do_override()
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/_distutils_hack/__init__.py", line 77, in do_override
    ensure_local_distutils()
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/_distutils_hack/__init__.py", line 64, in ensure_local_distutils
    assert '_distutils' in core.__file__, core.__file__
AssertionError: /opt/homebrew/Cellar/python@3.10/3.10.10_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/distutils/core.py
```

**Details**
When I interrupt the program, I get the following stack trace:
```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/forecasting/compose/_pipeline.py", line 386, in __init__
    self.steps_ = self._check_steps(steps, allow_postproc=False)
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/forecasting/compose/_pipeline.py", line 72, in _check_steps
    estimator_tuples = self._get_estimator_tuples(estimators, clone_ests=True)
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/base/_meta.py", line 372, in _get_estimator_tuples
    ests = self._get_estimator_list(estimators)
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/base/_meta.py", line 335, in _get_estimator_list
    return [self._coerce_estimator_tuple(x)[1] for x in estimators]
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/base/_meta.py", line 335, in <listcomp>
    return [self._coerce_estimator_tuple(x)[1] for x in estimators]
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/forecasting/base/_base.py", line 278, in __getitem__
    return ColumnSelect(key) ** self
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/forecasting/base/_base.py", line 206, in __rpow__
    self_as_pipeline = ForecastingPipeline(steps=[self])
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/forecasting/compose/_pipeline.py", line 386, in __init__
    self.steps_ = self._check_steps(steps, allow_postproc=False)
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/forecasting/compose/_pipeline.py", line 72, in _check_steps
    estimator_tuples = self._get_estimator_tuples(estimators, clone_ests=True)
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/base/_meta.py", line 374, in _get_estimator_tuples
    ests = [e.clone() for e in ests]
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/base/_meta.py", line 374, in <listcomp>
    ests = [e.clone() for e in ests]
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/base/_base.py", line 140, in clone
    return clone(self)
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sklearn/base.py", line 90, in clone
    new_object = klass(**new_object_params)
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/forecasting/compose/_pipeline.py", line 799, in __init__
    super(TransformedTargetForecaster, self).__init__()
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/forecasting/base/_base.py", line 118, in __init__
    _check_estimator_deps(self)
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/utils/validation/_dependencies.py", line 358, in _check_estimator_deps
    compatible = compatible and _check_python_version(obj, severity=severity)
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/utils/validation/_dependencies.py", line 263, in _check_python_version
    est_specifier_tag = obj.get_class_tag("python_version", tag_value_default="None")
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/base/_base.py", line 297, in get_class_tag
    collected_tags = cls.get_class_tags()
  File "/Users/kgeis/temp/mystuff/sp_prob/lib/python3.10/site-packages/sktime/base/_base.py", line 278, in get_class_tags
    return deepcopy(collected_tags)
  File "/opt/homebrew/Cellar/python@3.10/3.10.10_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/copy.py", line 146, in deepcopy
    y = copier(x, memo)
  File "/opt/homebrew/Cellar/python@3.10/3.10.10_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/copy.py", line 231, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
  File "/opt/homebrew/Cellar/python@3.10/3.10.10_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/copy.py", line 146, in deepcopy
    y = copier(x, memo)
KeyboardInterrupt

```


Discussion:
@RNKuhns 
Verified with 0.16.1, python 3.10.

Simplified, this seems to happen in cases where the pipelines are nested.

This surprises me, as we are explicitly testing for this in `test_nesting_pipelines` (and the other way round in `test_pipeline_with_dimension_changing_transformer`)

```python
from sktime.forecasting.arima import ARIMA
from sktime.forecasting.compose import ForecastingPipeline
from sktime.forecasting.compose import TransformedTargetForecaster

transformer = TransformedTargetForecaster(
        [
            ("arima", ARIMA()),
        ]
    )
forecaster = ForecastingPipeline(transformer)
```

Resolving patch:
diff --git a/sktime/forecasting/compose/_pipeline.py b/sktime/forecasting/compose/_pipeline.py
--- a/sktime/forecasting/compose/_pipeline.py
+++ b/sktime/forecasting/compose/_pipeline.py
@@ -5,6 +5,8 @@
 __author__ = ["mloning", "aiwalter"]
 __all__ = ["TransformedTargetForecaster", "ForecastingPipeline", "ForecastX"]
 
+from warnings import warn
+
 import pandas as pd
 
 from sktime.base import _HeterogenousMetaEstimator
@@ -69,6 +71,24 @@ def _check_steps(self, estimators, allow_postproc=False):
         TypeError if there is not exactly one forecaster in `estimators`
         TypeError if not allow_postproc and forecaster is not last estimator
         """
+        self_name = type(self).__name__
+        if not isinstance(estimators, list):
+            msg = (
+                f"steps in {self_name} must be list of estimators, "
+                f"or (string, estimator) pairs, "
+                f"the two can be mixed; but, found steps of type {type(estimators)}"
+            )
+            raise TypeError(msg)
+
+        if len(estimators) == 1:
+            msg = (
+                f"in {self_name}, found steps of length 1, "
+                f"this will result in the same behaviour "
+                f"as not wrapping the single step in a pipeline. "
+                f"Consider not wrapping steps in {self_name} as it is redundant."
+            )
+            warn(msg)
+
         estimator_tuples = self._get_estimator_tuples(estimators, clone_ests=True)
         names, estimators = zip(*estimator_tuples)
 
@@ -78,7 +98,7 @@ def _check_steps(self, estimators, allow_postproc=False):
         scitypes = self._get_pipeline_scitypes(estimator_tuples)
         if not set(scitypes).issubset(["forecaster", "transformer"]):
             raise TypeError(
-                f"estimators passed to {type(self).__name__} "
+                f"estimators passed to {self_name} "
                 f"must be either transformer or forecaster"
             )
         if scitypes.count("forecaster") != 1:
@@ -91,7 +111,7 @@ def _check_steps(self, estimators, allow_postproc=False):
 
         if not allow_postproc and forecaster_ind != len(estimators) - 1:
             TypeError(
-                f"in {type(self).__name__}, last estimator must be a forecaster, "
+                f"in {self_name}, last estimator must be a forecaster, "
                 f"but found a transformer"
             )
 


Test patch: