Goal
My program takes a yaml config file as parameter python myprogram.py cfg.yml
.
- all modules can access the cfg content with a simple module import
- the imported
config
behaves like the loaded top-level yaml dictionary for reading operations
# module_a.py
from config_module import cfg_class as config
print(config['pi'])
Reasoning
I wanted to follow the official python FAQ to share config global variables across modules but I could only define the parameters in a pre-defined file that cannot be changed by the user:
# config_module.py
pi = 3.14
To let the user choose a yaml config file, I changed config.py to:
# config_module.py
class cfg_class:
pass
and the very top of myprogram.py
to
# myprogram.py
from config_module import cfg_class as config
import yaml
if __name__=='__main__':
with open(sys.argv[1], 'r') as f:
cfg_dict = yaml.safe_load(f)
config.cfg_dict = cfg_dict
As a result all modules have access to the config content
# module_a.py
from config_module import cfg_class as config
print(config.cfg_dict['pi'])
Instead of using config.cfg_dict['pi']
I wanted to use config['pi']
. To do that I defined the __getitem__
for the cfg_class
:
class cfg_class:
def __getitem__(self, x):
return self.cfg_dict[x]
It failed with TypeError: 'type' object is not subscriptable
. An explanation to this problem is given here. It indicates that we need a metaclass for cfg_class:
# config.py
class Meta(type):
def __getitem__(self, x):
return cfg_class.cfg_dict[x]
class cfg_class(metaclass=Meta):
pass
And now it works. Below is the code for
config_module.py
myprogram.py
module_a.py
module_b.py
cfg.yml
Any feedback?
Working code
# config_module.py
class Meta(type):
def __getitem__(self, x):
return cfg_class.cfg_dict[x]
class cfg_class(metaclass=Meta):
pass
# myprogram.py
import sys
import yaml
from config_module import cfg_class as config
import module_a
import module_b
if __name__=='__main__':
with open(sys.argv[1], 'r') as f:
cfg_dict = yaml.safe_load(f)
config.cfg_dict = cfg_dict
module_a.a_class_from_module_a()
module_b.a_class_from_module_b()
# module_a.py
from config_module import cfg_class as config
class a_class_from_module_a:
def __init__(self):
print(
'I am an instance of a_class_from_module_a'
' and I have access to config: ',
config['pi']
)
# module_b.py
from config_module import cfg_class as config
class a_class_from_module_b:
def __init__(self):
print(
'I am an instance of a_class_from_module_b'
' and I have access to config: ',
config['pi']
)
# cfg.yml
---
pi: 3.14
...
Result:
$ python myprogram.py cfg.yml
>> I am an instance of a_class_from_module_a and I have access to config: 3.14
>> I am an instance of a_class_from_module_b and I have access to config: 3.14
Edit: simpler solution thanks to @MiguelAlorda who almost got it all right and @RootTwo who fixed the problem
# config_module.py
import yaml
config = {}
def load_config(file_str: str) -> None:
global config
with open(file_str) as f:
# config = yaml.safe_load(f) # does not work because it unbinds config, see comment from @RootTwo
config.update(yaml.safe_load(f))
# myprogram.py
import sys
import config_module
import module_a
import module_b
if __name__=='__main__':
config_module.load_config(sys.argv[1])
x = module_a.a_class_from_module_a()
y = module_b.a_class_from_module_b()
print(x,y)
# module_a.py
from config_module import config
class a_class_from_module_a:
def __init__(self):
print(
'I am an instance of a_class_from_module_a'
' and I have access to config: ',
config['pi']
)
# module_b.py
from config_module import config
class a_class_from_module_b:
def __init__(self):
print(
'I am an instance of a_class_from_module_b'
' and I have access to config: ',
config['pi']
)
#cfg.yml
---
pi: 3.14
...
Output:
$ python myprogram.py cfg.yml
I am an instance of a_class_from_module_a and I have access to config: 3.14
I am an instance of a_class_from_module_b and I have access to config: 3.14
<module_a.a_class_from_module_a object at 0x000002391D5F8F48> <module_b.a_class_from_module_b object at 0x000002391D5FB288>
1 Answer 1
welcome to Code Review!
I think this approach is a bit overkill. You could simply have a config
dict in the config_module
, and have a config_module.load_config(file_str: str) -> None
function:
# config_module.py
import yaml
config = {}
def load_config(file_str: str) -> None:
global config
with open(file_str) as f:
config.update(yaml.safe_load(f))
And then in you main:
import sys
import config_module
if __name__ == "__main__":
config_module.load_config(sys.argv[1])
Using the config:
from config_module import config
config["some_config_key"]