Repository: scikit-learn-contrib/imbalanced-learn

Issue:
[BUG] Imblearn not working if executed with python -OO
#### Describe the bug
If i try to execute a script that use imbalanced learn with python -OO the import fails with this error: 
```
Traceback (most recent call last):
  File "F:\main.py", line 10, in <module>
    from imblearn.over_sampling import SMOTE
  File "C:\Python39\lib\site-packages\imblearn\__init__.py", line 52, in <module>
    from . import combine
  File "C:\Python39\lib\site-packages\imblearn\combine\__init__.py", line 5, in <module>
    from ._smote_enn import SMOTEENN
  File "C:\Python39\lib\site-packages\imblearn\combine\_smote_enn.py", line 11, in <module>
    from ..over_sampling import SMOTE
  File "C:\Python39\lib\site-packages\imblearn\over_sampling\__init__.py", line 6, in <module>
    from ._adasyn import ADASYN
  File "C:\Python39\lib\site-packages\imblearn\over_sampling\_adasyn.py", line 26, in <module>
    class ADASYN(BaseOverSampler):
  File "C:\Python39\lib\site-packages\imblearn\utils\_docstring.py", line 22, in __call__
    obj.__doc__ = obj.__doc__.format(**self.params)
AttributeError: 'NoneType' object has no attribute 'format'
```

#### Steps/Code to Reproduce

```python
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from imblearn.combine import SMOTETomek, SMOTEENN
```

#### Expected Results
Hello World

#### Actual Results
```
Traceback (most recent call last):
  File "F:\main.py", line 10, in <module>
    from imblearn.over_sampling import SMOTE
  File "C:\Python39\lib\site-packages\imblearn\__init__.py", line 52, in <module>
    from . import combine
  File "C:\Python39\lib\site-packages\imblearn\combine\__init__.py", line 5, in <module>
    from ._smote_enn import SMOTEENN
  File "C:\Python39\lib\site-packages\imblearn\combine\_smote_enn.py", line 11, in <module>
    from ..over_sampling import SMOTE
  File "C:\Python39\lib\site-packages\imblearn\over_sampling\__init__.py", line 6, in <module>
    from ._adasyn import ADASYN
  File "C:\Python39\lib\site-packages\imblearn\over_sampling\_adasyn.py", line 26, in <module>
    class ADASYN(BaseOverSampler):
  File "C:\Python39\lib\site-packages\imblearn\utils\_docstring.py", line 22, in __call__
    obj.__doc__ = obj.__doc__.format(**self.params)
AttributeError: 'NoneType' object has no attribute 'format'
```

#### Versions
```
System:
    python: 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)]
executable: C:\Python39\python.exe
   machine: Windows-10-10.0.19042-SP0

Python dependencies:
      sklearn: 1.1.2
          pip: 22.3
   setuptools: 56.0.0
        numpy: 1.23.2
        scipy: 1.9.1
       Cython: None
       pandas: 1.5.0
   matplotlib: 3.5.3
       joblib: 1.1.0
threadpoolctl: 3.1.0

Built with OpenMP: True

threadpoolctl info:
       user_api: openmp
   internal_api: openmp
         prefix: vcomp
       filepath: C:\Python39\Lib\site-packages\sklearn\.libs\vcomp140.dll
        version: None
    num_threads: 16

       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: C:\Python39\Lib\site-packages\numpy\.libs\libopenblas.FB5AE2TYXYH2IJRDKGDGQ3XBKLKTF43H.gfortran-win_amd64.dll
        version: 0.3.20
threading_layer: pthreads
   architecture: Haswell
    num_threads: 16

       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: C:\Python39\Lib\site-packages\scipy\.libs\libopenblas.PZA5WNOTOH6FZLB2KBVKAURAKVTFSNNU.gfortran-win_amd64.dll
        version: 0.3.18
threading_layer: pthreads
   architecture: Haswell
    num_threads: 16
```


Discussion:

Patch:
diff --git a/doc/whats_new/v0.10.rst b/doc/whats_new/v0.10.rst
index 3e75e01..8a6f647 100644
--- a/doc/whats_new/v0.10.rst
+++ b/doc/whats_new/v0.10.rst
@@ -6,6 +6,13 @@ Version 0.10.0 (ongoing)
 Changelog
 ---------
 
+Bug fixes
+.........
+
+- Make sure that :class:`~imblearn.utils._docstring.Substitution` is
+  working with `python -OO` that replace `__doc__` by `None`.
+  :pr:`953` bu :user:`Guillaume Lemaitre <glemaitre>`.
+
 Compatibility
 .............
 
diff --git a/imblearn/utils/_docstring.py b/imblearn/utils/_docstring.py
index d03be37..61921c3 100644
--- a/imblearn/utils/_docstring.py
+++ b/imblearn/utils/_docstring.py
@@ -19,7 +19,8 @@ class Substitution:
         self.params = args or kwargs
 
     def __call__(self, obj):
-        obj.__doc__ = obj.__doc__.format(**self.params)
+        if obj.__doc__:
+            obj.__doc__ = obj.__doc__.format(**self.params)
         return obj
 
 


Test patch:
diff --git a/imblearn/utils/tests/test_docstring.py b/imblearn/utils/tests/test_docstring.py
index acfa1e1..0109fdb 100644
--- a/imblearn/utils/tests/test_docstring.py
+++ b/imblearn/utils/tests/test_docstring.py
@@ -66,3 +66,17 @@ def test_docstring_inject(obj, obj_docstring):
 def test_docstring_template():
     assert "random_state" in _random_state_docstring
     assert "n_jobs" in _n_jobs_docstring
+
+
+def test_docstring_with_python_OO():
+    """Check that we don't raise a warning if the code is executed with -OO.
+
+    Non-regression test for:
+    https://github.com/scikit-learn-contrib/imbalanced-learn/issues/945
+    """
+    instance = cls(param_1="xxx", param_2="yyy")
+    instance.__doc__ = None  # simulate -OO
+
+    instance = Substitution(param_1="xxx", param_2="yyy")(instance)
+
+    assert instance.__doc__ is None