Repository: open-mmlab/mmcv

Issue:
call Config.fromfile in a config file
If we want to define a config based on another config, the current version seems to raise AttributeError. Here is an example to reproduce the error.
`./config_a.py`
```
a = 1
b = 1
```
`./config_b.py`
```
from mmcv import Config
cfg = Config.fromfile('./config_a.py')
a = cfg.a + cfg.b
```
`./test.py`
```
from mmcv import Config
cfg = Config.fromfile('./config_b.py')
# do something according to cfg.a
```
When run with `python test.py`, it throws the following message.
```
Traceback (most recent call last):
  File "test.py", line 3, in <module>
    cfg = Config.fromfile('./config_b.py')
  File "/home/yanglei/anaconda3/lib/python3.6/site-packages/mmcv/utils/config.py", line 152, in fromfile
    cfg_dict, cfg_text = Config._file2dict(filename)
  File "/home/yanglei/anaconda3/lib/python3.6/site-packages/mmcv/utils/config.py", line 91, in _file2dict
    mod = import_module('_tempconfig')
  File "/home/yanglei/anaconda3/lib/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/tmp/tmpr6yo3isa/_tempconfig.py", line 3, in <module>
  File "/home/yanglei/anaconda3/lib/python3.6/site-packages/mmcv/utils/config.py", line 264, in __getattr__
    return getattr(self._cfg_dict, name)
  File "/home/yanglei/anaconda3/lib/python3.6/site-packages/mmcv/utils/config.py", line 34, in __getattr__
    raise ex
AttributeError: 'ConfigDict' object has no attribute 'a'
```
The issue may lie in https://github.com/open-mmlab/mmcv/blob/master/mmcv/utils/config.py#L89,
where a fixed temp name is used. Although `tempfile.TemporaryDirectory` creates different directories, the `sys.path` works as a global variable.

When we call `Config.fromfile` in `test.py`, `/tmp/tmpxxx1` is inserted to `sys.path`. When it comes to `Config.fromfile` in `config_b.py`, `/tmp/tmpxxx2` is inserted to `sys.path`. It may confuse `import_module('_tempconfig')` as `'_tempconfig.py'` exists under both directories.

A possible solution may use `osp.basename(filename)` to replace `'_tempconfig'`. But it is still likely that users call `Config.fromfile('./xx/config_b.py')` in `./config_b.py`, leading to a name conflict in `import_module`.

Is there any other better ways to solve it?


Discussion:

Resolving patch:
diff --git a/mmcv/utils/config.py b/mmcv/utils/config.py
--- a/mmcv/utils/config.py
+++ b/mmcv/utils/config.py
@@ -85,10 +85,14 @@ def _file2dict(filename):
         check_file_exist(filename)
         if filename.endswith('.py'):
             with tempfile.TemporaryDirectory() as temp_config_dir:
+                temp_config_file = tempfile.NamedTemporaryFile(
+                    dir=temp_config_dir, suffix='.py')
+                temp_config_name = osp.basename(temp_config_file.name)
                 shutil.copyfile(filename,
-                                osp.join(temp_config_dir, '_tempconfig.py'))
+                                osp.join(temp_config_dir, temp_config_name))
+                temp_module_name = osp.splitext(temp_config_name)[0]
                 sys.path.insert(0, temp_config_dir)
-                mod = import_module('_tempconfig')
+                mod = import_module(temp_module_name)
                 sys.path.pop(0)
                 cfg_dict = {
                     name: value
@@ -96,7 +100,9 @@ def _file2dict(filename):
                     if not name.startswith('__')
                 }
                 # delete imported module
-                del sys.modules['_tempconfig']
+                del sys.modules[temp_module_name]
+                # close temp file
+                temp_config_file.close()
         elif filename.endswith(('.yml', '.yaml', '.json')):
             import mmcv
             cfg_dict = mmcv.load(filename)


Test patch:
diff --git a/tests/data/config/code.py b/tests/data/config/code.py
new file mode 100644
--- /dev/null
+++ b/tests/data/config/code.py
@@ -0,0 +1,3 @@
+from mmcv import Config  # isort:skip
+cfg = Config.fromfile('./tests/data/config/a.py')
+item5 = cfg.item1[0] + cfg.item2.a
diff --git a/tests/test_config.py b/tests/test_config.py
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -134,6 +134,17 @@ def test_merge_intermediate_variable():
     assert cfg.item6 == dict(cfg=dict(b=2))
 
 
+def test_fromfile_in_config():
+    cfg_file = osp.join(osp.dirname(__file__), 'data/config/code.py')
+    cfg = Config.fromfile(cfg_file)
+    # cfg.field
+    assert cfg.cfg.item1 == [1, 2]
+    assert cfg.cfg.item2 == dict(a=0)
+    assert cfg.cfg.item3 is True
+    assert cfg.cfg.item4 == 'test'
+    assert cfg.item5 == 1
+
+
 def test_dict():
     cfg_dict = dict(item1=[1, 2], item2=dict(a=0), item3=True, item4='test')