API deprecate penalty='none' for LogisticRegression
diff --git a/doc/whats_new/v1.2.rst b/doc/whats_new/v1.2.rst
index 2e174eb5044b2..d8d5bc52aa13a 100644
--- a/doc/whats_new/v1.2.rst
+++ b/doc/whats_new/v1.2.rst
@@ -182,6 +182,10 @@ Changelog
   to `"highs"` in version 1.4.
   :pr:`23637` by :user:`Guillaume Lemaitre <glemaitre>`.
 
+- |API| String option `"none"` is deprecated for `penalty` argument
+  in :class:`linear_model.LogisticRegression`, and will be removed in version 1.4.
+  Use `None` instead. :pr:`23877` by :user:`Zhehao Liu <MaxwellLZH>`.
+
 :mod:`sklearn.metrics`
 ......................
 
diff --git a/sklearn/linear_model/_logistic.py b/sklearn/linear_model/_logistic.py
index 26b0f8e3370ef..e69d7dd6e5f3f 100644
--- a/sklearn/linear_model/_logistic.py
+++ b/sklearn/linear_model/_logistic.py
@@ -50,7 +50,8 @@
 
 def _check_solver(solver, penalty, dual):
 
-    if solver not in ["liblinear", "saga"] and penalty not in ("l2", "none"):
+    # TODO(1.4): Remove "none" option
+    if solver not in ["liblinear", "saga"] and penalty not in ("l2", "none", None):
         raise ValueError(
             "Solver %s supports only 'l2' or 'none' penalties, got %s penalty."
             % (solver, penalty)
@@ -766,10 +767,10 @@ class LogisticRegression(LinearClassifierMixin, SparseCoefMixin, BaseEstimator):
 
     Parameters
     ----------
-    penalty : {'l1', 'l2', 'elasticnet', 'none'}, default='l2'
+    penalty : {'l1', 'l2', 'elasticnet', None}, default='l2'
         Specify the norm of the penalty:
 
-        - `'none'`: no penalty is added;
+        - `None`: no penalty is added;
         - `'l2'`: add a L2 penalty term and it is the default choice;
         - `'l1'`: add a L1 penalty term;
         - `'elasticnet'`: both L1 and L2 penalty terms are added.
@@ -782,6 +783,10 @@ class LogisticRegression(LinearClassifierMixin, SparseCoefMixin, BaseEstimator):
         .. versionadded:: 0.19
            l1 penalty with SAGA solver (allowing 'multinomial' + L1)
 
+        .. deprecated:: 1.2
+           The 'none' option was deprecated in version 1.2, and will be removed
+           in 1.4. Use `None` instead.
+
     dual : bool, default=False
         Dual or primal formulation. Dual formulation is only implemented for
         l2 penalty with liblinear solver. Prefer dual=False when
@@ -846,11 +851,11 @@ class LogisticRegression(LinearClassifierMixin, SparseCoefMixin, BaseEstimator):
            The choice of the algorithm depends on the penalty chosen:
            Supported penalties by solver:
 
-           - 'newton-cg'   -   ['l2', 'none']
-           - 'lbfgs'       -   ['l2', 'none']
+           - 'newton-cg'   -   ['l2', None]
+           - 'lbfgs'       -   ['l2', None]
            - 'liblinear'   -   ['l1', 'l2']
-           - 'sag'         -   ['l2', 'none']
-           - 'saga'        -   ['elasticnet', 'l1', 'l2', 'none']
+           - 'sag'         -   ['l2', None]
+           - 'saga'        -   ['elasticnet', 'l1', 'l2', None]
 
         .. note::
            'sag' and 'saga' fast convergence is only guaranteed on
@@ -1012,7 +1017,11 @@ class LogisticRegression(LinearClassifierMixin, SparseCoefMixin, BaseEstimator):
     """
 
     _parameter_constraints = {
-        "penalty": [StrOptions({"l1", "l2", "elasticnet", "none"})],
+        # TODO(1.4): Remove "none" option
+        "penalty": [
+            StrOptions({"l1", "l2", "elasticnet", "none"}, deprecated={"none"}),
+            None,
+        ],
         "dual": ["boolean"],
         "tol": [Interval(Real, 0, None, closed="left")],
         "C": [Interval(Real, 0, None, closed="right")],
@@ -1106,10 +1115,18 @@ def fit(self, X, y, sample_weight=None):
                 "(penalty={})".format(self.penalty)
             )
 
+        # TODO(1.4): Remove "none" option
         if self.penalty == "none":
+            warnings.warn(
+                "`penalty='none'`has been deprecated in 1.2 and will be removed in 1.4."
+                " To keep the past behaviour, set `penalty=None`.",
+                FutureWarning,
+            )
+
+        if self.penalty is None or self.penalty == "none":
             if self.C != 1.0:  # default values
                 warnings.warn(
-                    "Setting penalty='none' will ignore the C and l1_ratio parameters"
+                    "Setting penalty=None will ignore the C and l1_ratio parameters"
                 )
                 # Note that check for l1_ratio is done right above
             C_ = np.inf
diff --git a/sklearn/linear_model/_stochastic_gradient.py b/sklearn/linear_model/_stochastic_gradient.py
index ad69a0b828f6b..7552fcc8f88b6 100644
--- a/sklearn/linear_model/_stochastic_gradient.py
+++ b/sklearn/linear_model/_stochastic_gradient.py
@@ -964,11 +964,11 @@ class SGDClassifier(BaseSGDClassifier):
             The loss 'log' was deprecated in v1.1 and will be removed
             in version 1.3. Use `loss='log_loss'` which is equivalent.
 
-    penalty : {'l2', 'l1', 'elasticnet'}, default='l2'
+    penalty : {'l2', 'l1', 'elasticnet', None}, default='l2'
         The penalty (aka regularization term) to be used. Defaults to 'l2'
         which is the standard regularizer for linear SVM models. 'l1' and
         'elasticnet' might bring sparsity to the model (feature selection)
-        not achievable with 'l2'.
+        not achievable with 'l2'. No penalty is added when set to `None`.
 
     alpha : float, default=0.0001
         Constant that multiplies the regularization term. The higher the
@@ -1773,11 +1773,11 @@ class SGDRegressor(BaseSGDRegressor):
             The loss 'squared_loss' was deprecated in v1.0 and will be removed
             in version 1.2. Use `loss='squared_error'` which is equivalent.
 
-    penalty : {'l2', 'l1', 'elasticnet'}, default='l2'
+    penalty : {'l2', 'l1', 'elasticnet', None}, default='l2'
         The penalty (aka regularization term) to be used. Defaults to 'l2'
         which is the standard regularizer for linear SVM models. 'l1' and
         'elasticnet' might bring sparsity to the model (feature selection)
-        not achievable with 'l2'.
+        not achievable with 'l2'. No penalty is added when set to `None`.
 
     alpha : float, default=0.0001
         Constant that multiplies the regularization term. The higher the
diff --git a/sklearn/linear_model/tests/test_logistic.py b/sklearn/linear_model/tests/test_logistic.py
index 0f0d635b5bb74..b9fee199380f3 100644
--- a/sklearn/linear_model/tests/test_logistic.py
+++ b/sklearn/linear_model/tests/test_logistic.py
@@ -1772,18 +1772,18 @@ def fit(X, y, **kw):
 
 @pytest.mark.parametrize("solver", ("lbfgs", "newton-cg", "sag", "saga"))
 def test_penalty_none(solver):
-    # - Make sure warning is raised if penalty='none' and C is set to a
+    # - Make sure warning is raised if penalty=None and C is set to a
     #   non-default value.
-    # - Make sure setting penalty='none' is equivalent to setting C=np.inf with
+    # - Make sure setting penalty=None is equivalent to setting C=np.inf with
     #   l2 penalty.
     X, y = make_classification(n_samples=1000, random_state=0)
 
-    msg = "Setting penalty='none' will ignore the C"
-    lr = LogisticRegression(penalty="none", solver=solver, C=4)
+    msg = "Setting penalty=None will ignore the C"
+    lr = LogisticRegression(penalty=None, solver=solver, C=4)
     with pytest.warns(UserWarning, match=msg):
         lr.fit(X, y)
 
-    lr_none = LogisticRegression(penalty="none", solver=solver, random_state=0)
+    lr_none = LogisticRegression(penalty=None, solver=solver, random_state=0)
     lr_l2_C_inf = LogisticRegression(
         penalty="l2", C=np.inf, solver=solver, random_state=0
     )
@@ -1971,3 +1971,16 @@ def test_single_feature_newton_cg():
     y = np.array([1, 1, 0, 0, 1, 1, 0, 1])
     assert X.shape[1] == 1
     LogisticRegression(solver="newton-cg", fit_intercept=True).fit(X, y)
+
+
+# TODO(1.4): Remove
+def test_warning_on_penalty_string_none():
+    # Test that warning message is shown when penalty='none'
+    target = iris.target_names[iris.target]
+    lr = LogisticRegression(penalty="none")
+    warning_message = (
+        "`penalty='none'`has been deprecated in 1.2 and will be removed in 1.4."
+        " To keep the past behaviour, set `penalty=None`."
+    )
+    with pytest.warns(FutureWarning, match=warning_message):
+        lr.fit(iris.data, target)

<!--
Thanks for contributing a pull request! Please ensure you have taken a look at
the contribution guidelines: https://github.com/scikit-learn/scikit-learn/blob/main/CONTRIBUTING.md
-->

#### Reference Issues/PRs
<!--
Example: Fixes #1234. See also #3456.
Please use keywords (e.g., Fixes) to create link to the issues or pull requests
you resolved, so that they will automatically be closed when your pull request
is merged. See https://github.com/blog/1506-closing-issues-via-pull-requests
-->
Fixes #23749.

#### What does this implement/fix? Explain your changes.
This PR deprecates `penalty='none'` for LogisticRegression, so the usage of `penalty=None` is consistent among `SGDClassifier`, `SGDRegressor`, `Perceptron` and `LogisticRegression`.

<!--
Please be aware that we are a loose team of volunteers so patience is
necessary; assistance handling other issues is very welcome. We value
all user contributions, no matter how minor they are. If we are slow to
review, either the pull request needs some benchmarking, tinkering,
convincing, etc. or more likely the reviewers are simply busy. In either
case, we ask for your understanding during the review process.
For more information, see our FAQ on this topic:
http://scikit-learn.org/dev/faq.html#why-is-my-pull-request-not-getting-any-attention.

Thanks for contributing!
-->
