问题
So I have this class:
import yaml
class Config():
def __init__(self, filename):
self.config_filename=filename
def __read_config_file(self):
with open(self.config_filename) as f:
self.cfg = yaml.safe_load(f)
def get(self):
self.__read_config_file()
return self.cfg
And it works fine. The thought behind it is to force a reread of the config file every time I use something in the configuration. Here is an example of usage:
cfg = Config('myconfig.yaml')
for name in cfg.get()['persons']:
print (cfg.get()['persons'][name]['phone'])
print (cfg.get()['persons'][name]['address'])
This works, but I think it looks extremely ugly. I could do something like this:
c = cfg.get()['persons']
for name in c:
print (c['persons'][name]['phone'])
print (c['persons'][name]['address'])
Which looks just a tiny bit better, but I also lose the benefit of reloading on access, but what I want to do is something this (which obviously does not work):
for name in c:
print (name['phone'])
print (name['address'])
It seems like it's something I don't understand about iterating over dictionaries, but my main concern here is that I want to reload the configuration file each time any value from that file is used, and I want it in a nice readable way. So how can I redesign this?
Example of configuration file. It's possible to change the format here if necessary.
persons:
john:
address: "street A"
phone: "123"
george:
address: "street B"
phone: "456"
回答1:
Since it is a dict, normal iteration doesn't work. we can iterate through the dictionary by the following method,
for name, person in c.items():
print(name)
print(person['phone'])
print(person['address'])
Hope this helps
回答2:
I have made your 'persons' data :
persons={}
persons["name"]={"john":{'phone':'123', 'address':'street A'},
"george":{'phone':'456', 'address':'street B'}}
Here is something interesting, you can get all the names that are written in "persons":
L_names = list(persons['name'].keys())
print(L_names) # returns ['john', 'george']
So if you get the data of each character :
L_names_data = []
for i in list(persons['name'].keys()):
L_names_data.append(persons['name'][i])
You can easily write what you wanted : (a nice and simple for loop)
for name_data in L_names_data:
print(name_data['address'])
print(name_data['phone'])
#returns :
#street A
#123
#street B
#456
The inconvenient is that you loose the 'name' info ('john' and 'george' strings don't appear in 'L_names_data' which is a list of dictionnaries).
回答3:
The user rasjani made some comments that helped me solve it. What I do is basically this:
import collections
class Config(collections.UserDict):
def __getitem__(self, key):
self.reload()
return super().__getitem__(key)
def reload(self):
with open(self.filename) as f:
c=yaml.safe_load(f)
super().__init__(c)
The actual class is more complicated with more error checking and features, but the above is what does the magic I asked about.
The method __getitem__
is called everytime you use the dictionary. So what I do is simply calling reload
before calling __getitem__
from the superclass.
One should be aware of one exception here.
a = Config('myconf.yml')
b = a['field'] # Will reload the configuration file
c = a # But this will not
d = c['field'] # However, this will
I thought about solving that by also modifying __getattribute__
in the same way, but I ended up with all sorts of problems with endless recursion, and in practice I am not sure if it is a problem worth solving, or even a problem at all.
来源:https://stackoverflow.com/questions/61652216/how-to-update-configuration-on-each-read