API don't default get_params output to None in the future
diff --git a/doc/whats_new/v0.22.rst b/doc/whats_new/v0.22.rst
index bcef08ff1881b..6819cad18e2e1 100644
--- a/doc/whats_new/v0.22.rst
+++ b/doc/whats_new/v0.22.rst
@@ -45,6 +45,14 @@ Changelog
     :pr:`123456` by :user:`Joe Bloggs <joeongithub>`.
     where 123456 is the *pull request* number, not the issue number.
 
+:mod:`sklearn.base`
+...................
+
+- |API| From version 0.24 :meth:`BaseEstimator.get_params` will raise an
+  AttributeError rather than return None for parameters that are in the
+  estimator's constructor but not stored as attributes on the instance.
+  :pr:`14464` by `Joel Nothman`_.
+
 :mod:`sklearn.calibration`
 ..........................
 
@@ -112,6 +120,14 @@ Changelog
   `predict_proba` give consistent results.
   :pr:`14114` by :user:`Guillaume Lemaitre <glemaitre>`.
 
+:mod:`sklearn.gaussian_process`
+...............................
+
+- |API| From version 0.24 :meth:`Kernel.get_params` will raise an
+  AttributeError rather than return None for parameters that are in the
+  estimator's constructor but not stored as attributes on the instance.
+  :pr:`14464` by `Joel Nothman`_.
+
 :mod:`sklearn.linear_model`
 ...........................
 
diff --git a/sklearn/base.py b/sklearn/base.py
index fb0818efc8248..dfc334a0efdc3 100644
--- a/sklearn/base.py
+++ b/sklearn/base.py
@@ -193,7 +193,15 @@ def get_params(self, deep=True):
         """
         out = dict()
         for key in self._get_param_names():
-            value = getattr(self, key, None)
+            try:
+                value = getattr(self, key)
+            except AttributeError:
+                warnings.warn('From version 0.24, get_params will raise an '
+                              'AttributeError if a parameter cannot be '
+                              'retrieved as an instance attribute. Previously '
+                              'it would return None.',
+                              FutureWarning)
+                value = None
             if deep and hasattr(value, 'get_params'):
                 deep_items = value.get_params().items()
                 out.update((key + '__' + k, val) for k, val in deep_items)
diff --git a/sklearn/gaussian_process/kernels.py b/sklearn/gaussian_process/kernels.py
index 88e97deaecf54..f1e857b4db255 100644
--- a/sklearn/gaussian_process/kernels.py
+++ b/sklearn/gaussian_process/kernels.py
@@ -23,6 +23,7 @@
 from collections import namedtuple
 import math
 from inspect import signature
+import warnings
 
 import numpy as np
 from scipy.special import kv, gamma
@@ -157,7 +158,16 @@ def get_params(self, deep=True):
                                " %s doesn't follow this convention."
                                % (cls, ))
         for arg in args:
-            params[arg] = getattr(self, arg, None)
+            try:
+                value = getattr(self, arg)
+            except AttributeError:
+                warnings.warn('From version 0.24, get_params will raise an '
+                              'AttributeError if a parameter cannot be '
+                              'retrieved as an instance attribute. Previously '
+                              'it would return None.',
+                              FutureWarning)
+                value = None
+            params[arg] = value
         return params
 
     def set_params(self, **params):
diff --git a/sklearn/gaussian_process/tests/test_kernels.py b/sklearn/gaussian_process/tests/test_kernels.py
index 7dd9c9900fcda..7c5614d917a69 100644
--- a/sklearn/gaussian_process/tests/test_kernels.py
+++ b/sklearn/gaussian_process/tests/test_kernels.py
@@ -14,7 +14,7 @@
 from sklearn.gaussian_process.kernels \
     import (RBF, Matern, RationalQuadratic, ExpSineSquared, DotProduct,
             ConstantKernel, WhiteKernel, PairwiseKernel, KernelOperator,
-            Exponentiation)
+            Exponentiation, Kernel)
 from sklearn.base import clone
 
 from sklearn.utils.testing import (assert_almost_equal,
@@ -323,3 +323,24 @@ def test_repr_kernels(kernel):
     # Smoke-test for repr in kernels.
 
     repr(kernel)
+
+
+def test_warns_on_get_params_non_attribute():
+    class MyKernel(Kernel):
+        def __init__(self, param=5):
+            pass
+
+        def __call__(self, X, Y=None, eval_gradient=False):
+            return X
+
+        def diag(self, X):
+            return np.ones(X.shape[0])
+
+        def is_stationary(self):
+            return False
+
+    est = MyKernel()
+    with pytest.warns(FutureWarning, match='AttributeError'):
+        params = est.get_params()
+
+    assert params['param'] is None
diff --git a/sklearn/pipeline.py b/sklearn/pipeline.py
index c66e37761782d..3d0207a4c16fc 100644
--- a/sklearn/pipeline.py
+++ b/sklearn/pipeline.py
@@ -127,9 +127,9 @@ class Pipeline(_BaseComposition):
 
     def __init__(self, steps, memory=None, verbose=False):
         self.steps = steps
-        self._validate_steps()
         self.memory = memory
         self.verbose = verbose
+        self._validate_steps()
 
     def get_params(self, deep=True):
         """Get parameters for this estimator.
diff --git a/sklearn/tests/test_base.py b/sklearn/tests/test_base.py
index 032d9b232523f..c47925b22c92e 100644
--- a/sklearn/tests/test_base.py
+++ b/sklearn/tests/test_base.py
@@ -505,3 +505,18 @@ def test_regressormixin_score_multioutput():
            "built-in scorer 'r2' uses "
            "multioutput='uniform_average').")
     assert_warns_message(FutureWarning, msg, reg.score, X, y)
+
+
+def test_warns_on_get_params_non_attribute():
+    class MyEstimator(BaseEstimator):
+        def __init__(self, param=5):
+            pass
+
+        def fit(self, X, y=None):
+            return self
+
+    est = MyEstimator()
+    with pytest.warns(FutureWarning, match='AttributeError'):
+        params = est.get_params()
+
+    assert params['param'] is None

#### Reference Issues/PRs

Fixes #14461 

#### What does this implement/fix? Explain your changes.

Previously, the default implementation of `get_params` would use `getattr` to retrieve a parameter value from an estimator, but would default to retrieve None if an AttributeError would otherwise have been raised.

This can lead to surprising behaviour upon `clone` when `__init__` is not correctly defined, as shown in #14461.

I propose that we raise an AttributeError when a param cannot be retrieved by `getattr`. This PR deprecates returning None, and raises a FutureWarning.

#### Any other comments?

TODO: add tests
