3
\$\begingroup\$

The goal of my current piece of work is to read from a global config file all the settings and then from the local file only those settings that a user has chosen.

Firstly I wrote a function but then I needed a class. It's my first working class in Python, and I ask if you can kindly note its defects. Although it works, I think it can for sure be written more correctly and concisely.

Here's all the beginning:

# files to be read from
cmd_parameter = "config_1.cfg"
cmd_parameter_global = "global.cfg"
if len(sys.argv) == 2:
 cmd_parameter = sys.argv[1]
else:
 print("Error: there should be one argument after program name - config file name")
 exit(-1)
config_file = []
config_file_global = []
# reading user's config file
try:
 with open(cmd_parameter, 'r') as config_file_name:
 config_file = config_file_name.readlines()
except FileNotFoundError:
 print("File '", sys.argv[1], "' not exists or not available")
 exit(-1)
# reading global config file
try:
 with open(cmd_parameter_global, 'r') as config_file_global_name:
 config_file_global = config_file_global_name.readlines()
except FileNotFoundError:
 print("Global configuration file '", cmd_parameter_global, "' not exists or not available")
 exit(-1)
# parsing config file, reading values from it to list and matching parameters with a given argument
# parameters in config file should start with <conf_key_split1>, value should be separated from parameter by <conf_key_split2>
config_param_user = []
config_value_user = []
config_param_global = []
config_value_global = []
config = ""
conf_key_split1 = '.'
conf_key_split2 = '='
conf_key_split3 = ','
# reading user's config file
for line in config_file:
 if line.startswith(conf_key_split1):
 line = line.rstrip().split(conf_key_split1)
 line = line[1]
 line = line.rstrip().split(conf_key_split2)
 config_param_user.append(line[0])
 config_value_user.append(line[1])
# reading global config file
for line in config_file_global:
 if line.startswith(conf_key_split1):
 line = line.rstrip().split(conf_key_split1)
 line = line[1]
 line = line.rstrip().split(conf_key_split2)
 config_param_global.append(line[0])
 config_value_global.append(line[1])

And here is the class itself:

# class for reading config from user's and global config file the list of params names list and the list of values
class configFromFile():
 def config_from_file_userfile(self, param):
 index = config_param_user.index(param)
 if config_value_user[index]:
 config_value = config_value_user[index]
 else:
 print('The value of the parameter "', param, '" was not found in', cmd_parameter)
 exit(-1)
 return config_value
 def config_from_file_global(self, param):
 index = config_param_global.index(param)
 if config_value_global[index]:
 config_value = config_value_global[index]
 else:
 print('The value of the parameter "', param, '" was not found in', cmd_parameter_global)
 exit(-1)
 return config_value
 def main(self, param):
 if param in config_param_global:
 config = self.config_from_file_global(param)
 elif param in config_param_user:
 config = self.config_from_file_userfile(param)
 else:
 print('The parameter "', param, '" was not found in', cmd_parameter, 'and in', cmd_parameter_global)
 exit(-1)
 return config

Usage:

names = configFromFile().main('names')
jonrsharpe
14k2 gold badges36 silver badges62 bronze badges
asked Jan 1, 2015 at 12:21
\$\endgroup\$
2

1 Answer 1

5
\$\begingroup\$

Firstly, you state:

I wrote a function but then I needed a class

Looking at the class you wrote, I would say you were incorrect there. The class has no __init__, holds no state, and only uses self to access other methods on the class. There is absolutely no reason for it to be a class.


Duplication is the programmer's arch enemy - every time you have the same thing written out more than once is an opportunity to reduce the amount of code you write, test and maintain. For example:

# reading user's config file
try:
 with open(cmd_parameter, 'r') as config_file_name:
 config_file = config_file_name.readlines()
except FileNotFoundError:
 print("File '", sys.argv[1], "' not exists or not available")
 exit(-1)
# reading global config file
try:
 with open(cmd_parameter_global, 'r') as config_file_global_name:
 config_file_global = config_file_global_name.readlines()
except FileNotFoundError:
 print("Global configuration file '", cmd_parameter_global, "' not exists or not available")
 exit(-1)

Note that these do more-or-less exactly the same thing, and should therefore be refactored to something like:

def extract_file(filename, message):
 try:
 with open(filename) as file_:
 return file_.readlines()
 except FileNotFoundError:
 print(message.format(filename))
 exit(-1)

and then called like:

config_file = extract_file(cmd_parameter,
 "File '{}' not exists or not available")

You should apply this throughout your code - you have basically written the same program twice, with minor adjustments, for the two config files, which makes no sense.


The dict is a key data structure in Python, and would make all of your code vastly simpler. You could create a dictionary of the global configuration options, then update it with the local values. Lookups by name would then be O(1), rather than O(n), and you could get rid of all the indexing.


In terms of general structure, you have lots of code running at the top level of the script. This is generally a bad idea, and will give you problems if you later want to import this functionality elsewhere. You should define an entry point (usually, but not necessarily, called main) that takes the file locations as parameters and call it something like:

if __name__ == "__main__":
 if len(sys.argv) == 2:
 main('global.cfg', sys.argv[1])
 else:
 print("Error: there should be one argument after program name - config file name")
 exit(-1)
answered Jan 1, 2015 at 13:55
\$\endgroup\$
8
  • \$\begingroup\$ Thanks a lot for answering. About code at the top level - I'm currently writing functions, and what you've written is very handy for me now. I'll probably leave the class because it is comfortable for me to have structure like that when I can just add more methods inside if there would be, say, more config files. \$\endgroup\$ Commented Jan 1, 2015 at 14:23
  • \$\begingroup\$ @nonrandom_passer but those methods are all duplicates. If you adopt the dictionary version, all you need is one function to parse a specified file to a config dictionary, then dict.update in the correct order. You could also use an existing file format, e.g. JSON, rather than define your own. \$\endgroup\$ Commented Jan 1, 2015 at 14:26
  • \$\begingroup\$ First of all, thanks again for your remarks. I used the dict and updated the main question, pls take a look. And I also would appreciate if you'll comment on 2 more things. First is an entry point that you've mentioned (sorry, I didn't quite got it - did you mean to put all the main body of a script into a main method? \$\endgroup\$ Commented Jan 3, 2015 at 19:46
  • \$\begingroup\$ And the second is that I've tried to put all imports inside try-except blocks that I have also in the beginning to one function but did not succeed. Can you please comment? def import_module(module_name, custom_module_name, message): try: import module_name as custom_module_name except ImportError: print(message.format(module_name)) import_module("random", "rnd", "Importing module '{}' did not succeed") \$\endgroup\$ Commented Jan 3, 2015 at 19:49
  • \$\begingroup\$ Putting the whole script inside a main is one way to do it, but it would be much better to split into lots of short functions, then have a main that just calls the appropriate functions at the appropriate times. \$\endgroup\$ Commented Jan 3, 2015 at 19:49

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.