Python 3
The script currently does not work in Python 3. For example, print "converted =" + str(converted)
is invalid syntax in Python 3, use print()
instead (as a function).
Comments
It's great that you document the code. Some comments however don't add any value for future reading: #this took me a few hour to figure out a bug. Thanks to stackoverflow
. I have no idea what the bug is, and what the answer from StackOverflow was.
Try to use docstrings as well. For instance in the hex2dec function:
def hex2dec(hex):
"""Convert a hexadecimal string to a decimal number"""
result_dec = int(hex, 0)
return result_dec
Some other things about the above function:
- Rather than
hex2dec
, perhaps use hex_to_decimal
. In this case it's not too big of a deal though.
- You're storing the result in a variable and then returning the variable without doing anything else to it. This means you could also return result immediately:
return int(hex, 0)
- Since it's using a built in function without any other operation, is the function necessary?
Running it!
As said above, using Python 3 failed to run it. Changing the print statements to functions fixed this issue. Python 2 also supports the print()
function, so it should have no issue running on Python 2 as well.
The program ran as expected with a correct file, and produced an output file with the converted values.
Running it with an invalid file, or no arguments at all, crashes the program with either a FileNotFoundError
or ValueError
respectively. If a file was given which exists but has an invalid hexadecimal value in it (for instance a random string), the program also crashes:
Traceback (most recent call last):
File "hex2dec.py", line 22, in <module>
converted = str(hex2dec (i))
File "hex2dec.py", line 7, in hex2dec
result_dec = int(hex, 0)
ValueError: invalid literal for int() with base 0: 'random word'
The above errors tells us we need some error checking.
The first error to solve would be the ValueError
of not giving a filename as argument when running the program. This can be solved by using a CLI module such as argparse
, click
, or perhaps another one you like. I personally use argparse, since it's pretty simple and it's part of the standard library.
The arguments:
import argparse
def get_cli_parser():
parser = argparse.ArgumentParser(description='Describe what your program does')
parser.add_argument('input_file', type=str,
help='The file to read the hexadecimal values from'
return parser
if __name__ == '__main__':
parser = get_cli_parser()
args = vars(parser.parse_args())
At this point you can access the command line arguments args
as a dictionary.
The if __name__ == '__main__':
part of the code is only applicable if it's run as a script. This means that if the file is imported as a module, this will not run. See also this answer for more info.
When we run the above code without any arguments, we get the following:
usage: hex2dec2.py [-h] input_file
hex2dec2.py: error: the following arguments are required: input_file
Next up is the FileNotFoundError
. This one is quite easy to solve: before opening the file, check if it exists using the already imported os
module:
if not os.path.exists(args['input_file']):
print('Input file does not exist, please try again')
return
The last ValueError
can be solved in a number of ways. One would be to use regex on the current line of the file to find any hexadecimal values. Assuming only the hexadecimal values should be on a line, it becomes a lot simpler though. We could just check if the line starts with 0x
(e.g. a hexadecimal value).
Reading the file should also be done line by line as an iterator, to prevent huge files from crashing your program by too high memory consumption. Right now the program reads all the text in the file into memory. This can be done as described in the following question
:
with open(input_file) as input_file:
for line in input_file:
# rest of code here.
Placing this in a function of its own also prevents you from opening the file continuously.
The final product
import argparse
import os
def write_and_print_result(args, result):
"""Write every result to the output file
and print each converted value
"""
out_file = args['input_file'][:-4] + "_1.txt"
with open(out_file, 'a+') as output:
for converted in result:
output.write(converted + "\n")
print('Converted: {}'.format(converted))
def read_and_convert_file(args):
"""Read the given input file and convert each hexadecimal value
to decimal value
"""
result = []
with open(args['input_file']) as input_file:
for line in input_file:
if (line.startswith('0x')):
result.append(str(int(line, 0)))
write_and_print_result(args, result)
def get_cli_parser():
parser = argparse.ArgumentParser(description="Describe program here")
parser.add_argument('input_file', type=str,
help='The file to read the hexadecimal values from')
return parser
def cli_runner():
parser = get_cli_parser()
args = vars(parser.parse_args())
if not os.path.exists(args['input_file']):
print('Input file does not exist, please try again.')
return
read_and_convert_file(args)
if __name__ == '__main__':
cli_runner()
As you can see, most of it is either error checking or command line arguments. The actual conversion is only one line of code.
What's next
One issue I still found:
- If the input file is anything but
*.txt
, the output file might have a weird name. For example, a file called hex
would simply become _1.txt
, which isn't very descriptive. To fix this, as pointed out by @Mathias Ettinger, you could use os.path.splitext
. It is used as such:
filename, extension = os.path.splitext(args['input_file'])
outfile = filename + "_1.txt"
sh -c 'while read x; do printf "%d\n" $x; done' < hex.txt
\$\endgroup\$printf
command once per line):awk '{ printf "%d\n", $1 }' < hex.txt
\$\endgroup\$