Repository: open-mmlab/mmeval (language: python)

Issue:
A 'try_import' utils function should be used
        These statements appear in many places and it is recommended to encapsulate them into a function.

_Originally posted by @zhouzaida in https://github.com/open-mmlab/mmeval/pull/7#discussion_r972765045_
      


Discussion / hints:

Fix patch:
diff --git a/mmeval/__init__.py b/mmeval/__init__.py
index 542baeb1..d8a7039f 100644
--- a/mmeval/__init__.py
+++ b/mmeval/__init__.py
@@ -4,4 +4,5 @@
 
 from .core import *
 from .metrics import *
+from .utils import *
 from .version import __version__
diff --git a/mmeval/core/dist_backends/mpi4py.py b/mmeval/core/dist_backends/mpi4py.py
index 97546d58..3053f356 100644
--- a/mmeval/core/dist_backends/mpi4py.py
+++ b/mmeval/core/dist_backends/mpi4py.py
@@ -1,14 +1,15 @@
 # Copyright (c) OpenMMLab. All rights reserved.
 
 import os
-from typing import Any, List
+from typing import TYPE_CHECKING, Any, List
 
+from mmeval.utils import try_import
 from .base_backend import BaseDistBackend
 
-try:
+if TYPE_CHECKING:
     from mpi4py import MPI
-except ImportError:
-    MPI = None
+else:
+    MPI = try_import('mpi4py.MPI')
 
 
 class MPI4PyDist(BaseDistBackend):
diff --git a/mmeval/core/dist_backends/tf_horovod.py b/mmeval/core/dist_backends/tf_horovod.py
index 89bc9c35..53799767 100644
--- a/mmeval/core/dist_backends/tf_horovod.py
+++ b/mmeval/core/dist_backends/tf_horovod.py
@@ -1,13 +1,14 @@
 # Copyright (c) OpenMMLab. All rights reserved.
 
-from typing import Any, List
+from typing import TYPE_CHECKING, Any, List
 
+from mmeval.utils import try_import
 from .base_backend import BaseDistBackend
 
-try:
+if TYPE_CHECKING:
     import horovod.tensorflow as hvd
-except ImportError:
-    hvd = None
+else:
+    hvd = try_import('horovod.tensorflow')
 
 
 class TFHorovodDist(BaseDistBackend):
diff --git a/mmeval/core/dist_backends/torch_cpu.py b/mmeval/core/dist_backends/torch_cpu.py
index f8c0e64e..b9e73056 100644
--- a/mmeval/core/dist_backends/torch_cpu.py
+++ b/mmeval/core/dist_backends/torch_cpu.py
@@ -1,16 +1,17 @@
 # Copyright (c) OpenMMLab. All rights reserved.
 
 import pickle
-from typing import Any, List, Tuple, TypeVar, Union
+from typing import TYPE_CHECKING, Any, List, Tuple, TypeVar, Union
 
+from mmeval.utils import try_import
 from .base_backend import TensorBaseDistBackend
 
-try:
+if TYPE_CHECKING:
     import torch
     import torch.distributed as torch_dist
-except ImportError:
-    torch = None
-    torch_dist = None
+else:
+    torch = try_import('torch')
+    torch_dist = try_import('torch.distributed')
 
 Tensor = TypeVar('Tensor', bound='torch.Tensor')
 
diff --git a/mmeval/core/dist_backends/torch_cuda.py b/mmeval/core/dist_backends/torch_cuda.py
index 4e952cfa..00a5284b 100644
--- a/mmeval/core/dist_backends/torch_cuda.py
+++ b/mmeval/core/dist_backends/torch_cuda.py
@@ -1,13 +1,14 @@
 # Copyright (c) OpenMMLab. All rights reserved.
 
-from typing import Any, Tuple, TypeVar, Union
+from typing import TYPE_CHECKING, Any, Tuple, TypeVar, Union
 
+from mmeval.utils import try_import
 from .torch_cpu import TorchCPUDist
 
-try:
+if TYPE_CHECKING:
     import torch
-except ImportError:
-    torch = None
+else:
+    torch = try_import('torch')
 
 Tensor = TypeVar('Tensor', bound='torch.Tensor')
 
diff --git a/mmeval/metrics/accuracy.py b/mmeval/metrics/accuracy.py
index c4b04966..533b88aa 100644
--- a/mmeval/metrics/accuracy.py
+++ b/mmeval/metrics/accuracy.py
@@ -1,16 +1,17 @@
 # Copyright (c) OpenMMLab. All rights reserved.
 
 import numpy as np
-from typing import (Dict, Iterable, List, Optional, Sequence, Tuple, Union,
-                    overload)
+from typing import (TYPE_CHECKING, Dict, Iterable, List, Optional, Sequence,
+                    Tuple, Union, overload)
 
 from mmeval.core.base_metric import BaseMetric
 from mmeval.core.dispatcher import dispatch
+from mmeval.utils import try_import
 
-try:
+if TYPE_CHECKING:
     import torch
-except ImportError:
-    torch = None
+else:
+    torch = try_import('torch')
 
 
 @overload
diff --git a/mmeval/metrics/mean_iou.py b/mmeval/metrics/mean_iou.py
index abf1bf43..710e4c27 100644
--- a/mmeval/metrics/mean_iou.py
+++ b/mmeval/metrics/mean_iou.py
@@ -1,15 +1,16 @@
 # Copyright (c) OpenMMLab. All rights reserved.
 
 import numpy as np
-from typing import List, Optional, Sequence, Tuple, overload
+from typing import TYPE_CHECKING, List, Optional, Sequence, Tuple, overload
 
 from mmeval.core.base_metric import BaseMetric
 from mmeval.core.dispatcher import dispatch
+from mmeval.utils import try_import
 
-try:
+if TYPE_CHECKING:
     import torch
-except ImportError:
-    torch = None
+else:
+    torch = try_import('torch')
 
 
 class MeanIoU(BaseMetric):
diff --git a/mmeval/utils/__init__.py b/mmeval/utils/__init__.py
new file mode 100644
index 00000000..c7805617
--- /dev/null
+++ b/mmeval/utils/__init__.py
@@ -0,0 +1,5 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+
+from .misc import try_import
+
+__all__ = ['try_import']
diff --git a/mmeval/utils/misc.py b/mmeval/utils/misc.py
new file mode 100644
index 00000000..c3af2c60
--- /dev/null
+++ b/mmeval/utils/misc.py
@@ -0,0 +1,22 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+
+import importlib
+from types import ModuleType
+from typing import Optional
+
+
+def try_import(name: str) -> Optional[ModuleType]:
+    """Try to import a module.
+
+    Args:
+        name (str): Specifies what module to import in absolute or relative
+            terms (e.g. either pkg.mod or ..mod).
+
+    Returns:
+        ModuleType or None: If importing successfully, returns the imported
+        module, otherwise returns None.
+    """
+    try:
+        return importlib.import_module(name)
+    except ImportError:
+        return None


Test patch:
diff --git a/tests/test_core/test_base_metric.py b/tests/test_core/test_base_metric.py
index 0f034985..927e44ad 100644
--- a/tests/test_core/test_base_metric.py
+++ b/tests/test_core/test_base_metric.py
@@ -3,11 +3,9 @@
 import pytest
 
 from mmeval.core.base_metric import BaseMetric
+from mmeval.utils import try_import
 
-try:
-    import torch
-except ImportError:
-    torch = None
+torch = try_import('torch')
 
 
 class Mertic(BaseMetric):
diff --git a/tests/test_core/test_dispatcher.py b/tests/test_core/test_dispatcher.py
index a0708db6..8c0ab029 100644
--- a/tests/test_core/test_dispatcher.py
+++ b/tests/test_core/test_dispatcher.py
@@ -5,11 +5,9 @@
 from typing import Dict, List, overload
 
 from mmeval.core.dispatcher import _MMEvalDispatcher, dispatch
+from mmeval.utils import try_import
 
-try:
-    import torch
-except ImportError:
-    torch = None
+torch = try_import('torch')
 
 
 class TestMMEvalDispatcher:
diff --git a/tests/test_metrics/test_accuracy.py b/tests/test_metrics/test_accuracy.py
index 3aa737f6..3671f789 100644
--- a/tests/test_metrics/test_accuracy.py
+++ b/tests/test_metrics/test_accuracy.py
@@ -7,11 +7,9 @@
 
 from mmeval.core.base_metric import BaseMetric
 from mmeval.metrics import Accuracy
+from mmeval.utils import try_import
 
-try:
-    import torch
-except ImportError:
-    torch = None
+torch = try_import('torch')
 
 
 @pytest.mark.parametrize(
diff --git a/tests/test_metrics/test_mean_iou.py b/tests/test_metrics/test_mean_iou.py
index 93bf3b9b..2a13d655 100644
--- a/tests/test_metrics/test_mean_iou.py
+++ b/tests/test_metrics/test_mean_iou.py
@@ -7,11 +7,9 @@
 
 from mmeval.core.base_metric import BaseMetric
 from mmeval.metrics import MeanIoU
+from mmeval.utils import try_import
 
-try:
-    import torch
-except ImportError:
-    torch = None
+torch = try_import('torch')
 
 
 def test_metric_interface_numpy():
diff --git a/tests/test_utils/test_misc.py b/tests/test_utils/test_misc.py
new file mode 100644
index 00000000..e09ccebe
--- /dev/null
+++ b/tests/test_utils/test_misc.py
@@ -0,0 +1,15 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+
+import pytest
+
+from mmeval.utils import try_import
+
+
+def test_try_import():
+    import numpy as np
+    assert try_import('numpy') is np
+    assert try_import('numpy111') is None
+
+
+if __name__ == '__main__':
+    pytest.main([__file__, '-v', '--capture=no'])