45
45
import re # type: ignore
46
46
47
47
48
- def _lazy2string (cfg_dict , dict_type = None ):
49
- if isinstance (cfg_dict , dict ):
50
- dict_type = dict_type or type (cfg_dict )
51
- return dict_type ({k : _lazy2string (v ) for k , v in dict .items (cfg_dict )})
52
- elif isinstance (cfg_dict , (tuple , list )):
53
- return type (cfg_dict )(_lazy2string (v ) for v in cfg_dict )
54
- elif isinstance (cfg_dict , (LazyAttr , LazyObject )):
55
- return f'{ cfg_dict .module } .{ str (cfg_dict )} '
56
- else :
57
- return cfg_dict
48
+ class LazyContainerMeta (type ):
49
+
50
+ def __init__ (self , * args , ** kwargs ):
51
+ super ().__init__ (* args , ** kwargs )
52
+ self .lazy = False
53
+
54
+
55
+ class LazyContainerMixin (metaclass = LazyContainerMeta ):
56
+
57
+ def to_builtin (self , keep_lazy = False ):
58
+
59
+ def _to_builtin (cfg ):
60
+ if isinstance (cfg , dict ):
61
+ return dict ({k : _to_builtin (v ) for k , v in dict .items (cfg )})
62
+ elif isinstance (cfg , tuple ):
63
+ return tuple (_to_builtin (v ) for v in tuple .__iter__ (cfg ))
64
+ elif isinstance (cfg , list ):
65
+ return list (_to_builtin (v ) for v in list .__iter__ (cfg ))
66
+ elif isinstance (cfg , set ):
67
+ return {_to_builtin (v ) for v in set .__iter__ (cfg )}
68
+ elif isinstance (cfg , (LazyAttr , LazyObject )):
69
+ if not keep_lazy :
70
+ return f'{ cfg .module } .{ str (cfg )} '
71
+ else :
72
+ return cfg
73
+ else :
74
+ return cfg
75
+
76
+ return _to_builtin (self )
77
+
78
+ def build_lazy (self , value : Any ) -> Any :
79
+ """If class attribute ``lazy`` is False, the LazyObject will be built
80
+ and returned.
81
+
82
+ Args:
83
+ value (Any): The value to be built.
84
+
85
+ Returns:
86
+ Any: The built value.
87
+ """
88
+ if (isinstance (value , (LazyAttr , LazyObject ))
89
+ and not self .__class__ .lazy ):
90
+ value = value .build ()
91
+ return value
92
+
93
+ def __deepcopy__ (self , memo ):
94
+ return self .__class__ (
95
+ copy .deepcopy (item , memo ) for item in super ().__iter__ ())
96
+
97
+ def __copy__ (self ):
98
+ return self .__class__ (item for item in super ().__iter__ ())
99
+
100
+ def __iter__ (self ):
101
+ # Override `__iter__` to overwrite to support star unpacking
102
+ # `*cfg_list`
103
+ yield from map (self .build_lazy , super ().__iter__ ())
104
+
105
+ def __getitem__ (self , idx ):
106
+ try :
107
+ value = self .build_lazy (super ().__getitem__ (idx ))
108
+ except Exception as e :
109
+ raise e
110
+ else :
111
+ return value
112
+
113
+ def __eq__ (self , other ):
114
+ return all (a == b for a , b in zip (self , other ))
115
+
116
+ def __reduce_ex__ (self , proto ):
117
+ # Override __reduce_ex__ to avoid dump the built lazy object.
118
+ if digit_version (platform .python_version ()) < digit_version ('3.8' ):
119
+ return (self .__class__ , (tuple (i for i in super ().__iter__ ()), ),
120
+ None , None , None )
121
+ else :
122
+ return (self .__class__ , (tuple (i for i in super ().__iter__ ()), ),
123
+ None , None , None , None )
58
124
125
+ copy = __copy__
59
126
60
- class ConfigDict (Dict ):
127
+
128
+ class ConfigDict (LazyContainerMixin , Dict ):
61
129
"""A dictionary for config which has the same interface as python's built-
62
130
in dictionary and can be used as a normal dictionary.
63
131
@@ -72,7 +140,6 @@ class ConfigDict(Dict):
72
140
object during configuration parsing, and it should be set to False outside
73
141
the Config to ensure that users do not experience the ``LazyObject``.
74
142
"""
75
- lazy = False
76
143
77
144
def __init__ (__self , * args , ** kwargs ):
78
145
object .__setattr__ (__self , '__parent' , kwargs .pop ('__parent' , None ))
@@ -118,8 +185,14 @@ def __getattr__(self, name):
118
185
@classmethod
119
186
def _hook (cls , item ):
120
187
# avoid to convert user defined dict to ConfigDict.
121
- if type (item ) in (dict , OrderedDict ):
188
+ if isinstance (item , ConfigDict ):
189
+ return item
190
+ elif type (item ) in (dict , OrderedDict ):
122
191
return cls (item )
192
+ elif isinstance (item , LazyContainerMixin ):
193
+ return type (item )(
194
+ cls ._hook (elem )
195
+ for elem in super (LazyContainerMixin , item ).__iter__ ())
123
196
elif isinstance (item , (list , tuple )):
124
197
return type (item )(cls ._hook (elem ) for elem in item )
125
198
return item
@@ -150,11 +223,6 @@ def __copy__(self):
150
223
151
224
copy = __copy__
152
225
153
- def __iter__ (self ):
154
- # Implement `__iter__` to overwrite the unpacking operator `**cfg_dict`
155
- # to get the built lazy object
156
- return iter (self .keys ())
157
-
158
226
def get (self , key : str , default : Optional [Any ] = None ) -> Any :
159
227
"""Get the value of the key. If class attribute ``lazy`` is True, the
160
228
LazyObject will be built and returned.
@@ -201,20 +269,6 @@ def update(self, *args, **kwargs) -> None:
201
269
else :
202
270
self [k ].update (v )
203
271
204
- def build_lazy (self , value : Any ) -> Any :
205
- """If class attribute ``lazy`` is False, the LazyObject will be built
206
- and returned.
207
-
208
- Args:
209
- value (Any): The value to be built.
210
-
211
- Returns:
212
- Any: The built value.
213
- """
214
- if isinstance (value , (LazyAttr , LazyObject )) and not self .lazy :
215
- value = value .build ()
216
- return value
217
-
218
272
def values (self ):
219
273
"""Yield the values of the dictionary.
220
274
@@ -288,28 +342,29 @@ def __eq__(self, other):
288
342
return False
289
343
290
344
def _to_lazy_dict (self ):
291
- """Convert the ConfigDict to a normal dictionary recursively, and keep
292
- the ``LazyObject`` or ``LazyAttr`` object not built."""
345
+ # NOTE: Keep this function for backward compatibility.
293
346
294
- def _to_dict (data ):
295
- if isinstance (data , ConfigDict ):
296
- return {
297
- key : _to_dict (value )
298
- for key , value in Dict .items (data )
299
- }
300
- elif isinstance (data , dict ):
301
- return {key : _to_dict (value ) for key , value in data .items ()}
302
- elif isinstance (data , (list , tuple )):
303
- return type (data )(_to_dict (item ) for item in data )
304
- else :
305
- return data
306
-
307
- return _to_dict (self )
347
+ return self .to_builtin (keep_lazy = True )
308
348
309
349
def to_dict (self ):
310
- """Convert the ConfigDict to a normal dictionary recursively, and
311
- convert the ``LazyObject`` or ``LazyAttr`` to string."""
312
- return _lazy2string (self , dict_type = dict )
350
+ # NOTE: Keep this function for backward compatibility.
351
+ return self .to_builtin ()
352
+
353
+
354
+ class ConfigList (LazyContainerMixin , list ): # type: ignore
355
+
356
+ def pop (self , idx ):
357
+ return self .build_lazy (super ().pop (idx ))
358
+
359
+
360
+ class ConfigTuple (LazyContainerMixin , tuple ): # type: ignore
361
+ ...
362
+
363
+
364
+ class ConfigSet (LazyContainerMixin , set ): # type: ignore
365
+
366
+ def pop (self , idx ):
367
+ return self .build_lazy (super ().pop (idx ))
313
368
314
369
315
370
def add_args (parser : ArgumentParser ,
@@ -479,27 +534,30 @@ def fromfile(filename: Union[str, Path],
479
534
env_variables = env_variables ,
480
535
)
481
536
else :
482
- # Enable lazy import when parsing the config.
483
- # Using try-except to make sure ``ConfigDict.lazy`` will be reset
484
- # to False. See more details about lazy in the docstring of
485
- # ConfigDict
486
- ConfigDict .lazy = True
487
- try :
537
+ with Config ._lazy_context ():
488
538
cfg_dict , imported_names = Config ._parse_lazy_import (filename )
489
- except Exception as e :
490
- raise e
491
- finally :
492
- # disable lazy import to get the real type. See more details
493
- # about lazy in the docstring of ConfigDict
494
- ConfigDict .lazy = False
495
-
496
539
cfg = Config (
497
540
cfg_dict ,
498
541
filename = filename ,
499
542
format_python_code = format_python_code )
500
543
object .__setattr__ (cfg , '_imported_names' , imported_names )
501
544
return cfg
502
545
546
+ @staticmethod
547
+ @contextmanager
548
+ def _lazy_context ():
549
+ ConfigDict .lazy = True
550
+ ConfigSet .lazy = True
551
+ ConfigList .lazy = True
552
+ ConfigTuple .lazy = True
553
+
554
+ yield
555
+
556
+ ConfigDict .lazy = False
557
+ ConfigSet .lazy = False
558
+ ConfigList .lazy = False
559
+ ConfigTuple .lazy = False
560
+
503
561
@staticmethod
504
562
def fromstring (cfg_str : str , file_format : str ) -> 'Config' :
505
563
"""Build a Config instance from config text.
@@ -1110,12 +1168,12 @@ def _parse_lazy_import(filename: str) -> Tuple[ConfigDict, set]:
1110
1168
continue
1111
1169
ret [key ] = value
1112
1170
# convert dict to ConfigDict
1113
- cfg_dict = Config ._dict_to_config_dict_lazy (ret )
1171
+ cfg_dict = Config ._to_lazy_container (ret )
1114
1172
1115
1173
return cfg_dict , imported_names
1116
1174
1117
1175
@staticmethod
1118
- def _dict_to_config_dict_lazy (cfg : dict ):
1176
+ def _to_lazy_container (cfg : dict ):
1119
1177
"""Recursively converts ``dict`` to :obj:`ConfigDict`. The only
1120
1178
difference between ``_dict_to_config_dict_lazy`` and
1121
1179
``_dict_to_config_dict_lazy`` is that the former one does not consider
@@ -1131,11 +1189,15 @@ def _dict_to_config_dict_lazy(cfg: dict):
1131
1189
if isinstance (cfg , dict ):
1132
1190
cfg_dict = ConfigDict ()
1133
1191
for key , value in cfg .items ():
1134
- cfg_dict [key ] = Config ._dict_to_config_dict_lazy (value )
1192
+ cfg_dict [key ] = Config ._to_lazy_container (value )
1135
1193
return cfg_dict
1136
- if isinstance (cfg , (tuple , list )):
1137
- return type (cfg )(
1138
- Config ._dict_to_config_dict_lazy (_cfg ) for _cfg in cfg )
1194
+ if isinstance (cfg , list ):
1195
+ return ConfigList (Config ._to_lazy_container (_cfg ) for _cfg in cfg )
1196
+ if isinstance (cfg , tuple ):
1197
+ return ConfigTuple (Config ._to_lazy_container (_cfg ) for _cfg in cfg )
1198
+ if isinstance (cfg , set ):
1199
+ return ConfigSet (Config ._to_lazy_container (_cfg ) for _cfg in cfg )
1200
+
1139
1201
return cfg
1140
1202
1141
1203
@staticmethod
0 commit comments