Command-line-based capacitance decoding

I am an amateur programmer and have just written a simple Capacitor Identifier mostly to practice. I have not had many people review my coding and thought this would be a good time to start. It is completed and works. Is there a better way I should have went about it? What should I look at in what and how I am writing that might improve my approach?

#!/usr/bin/env python3

import sys

# Get the arguments list
cmdargs = sys.argv

cmdargs = cmdargs[1:]

tolerances = {'B':'.10%','C':'.25%','D':'.5%','E':'.5%','F':'1%','G':'2%','H':'3%','J':'5%','K':'10%','M':'20%','N':'.05%','P':'+100% to 0%','Z':'+80% to -20%'}

# Get the total number of args passed
total = len(cmdargs)

# Templete String for output
capTemp = '{} = {}'

# output container
result = []

# cycle through passed arguments and decode each capacitor
for arg in cmdargs:
case = len(arg)

if(case == 2):
temp = arg + ' pf, ' + str(float(arg) / 1000000) + 'uf'
result.append(capTemp.format(arg, temp))
elif(case == 3):
cap = int(arg[:2]) * 10**int(arg[2:])
temp = str(cap) + ' pf, ' + str(float(cap) / 1000000) + 'uf'
result.append(capTemp.format(arg, temp))

elif(case == 4):
cap = int(arg[:2]) * 10**int(arg[2:3])
temp = str(cap) + ' pf, ' + str(float(cap) / 1000000) + 'uf, ' + tolerances[arg[3:].upper()] + ' tolerance'
result.append(capTemp.format(arg, temp))

# print the decoded capacitor values
for i in result:
print(i)


Variable usage

Assign cmdargs once, and get it right the first time. Put a more insightful comment there. Rename it to cmd_args for readability.

# Skip argv[0], which is the name of this program
cmd_args = sys.argv[1:]


The total variable is never used.

The variable name case is not descriptive enough. Naming a variable temp is almost always a bad idea as well. I suggest names like arg_len and decoded_value, respectively.

tolerances is a constant, and should be named TOLERANCES by convention. Format it using one entry per line for readability.

Decoding technique

It is good practice to separate your calculations from your output routines. Start by defining a function decode_capacitance(code):

def decode_capacitance(code):
'''
Decodes the capacitance as a (value, tolerance) pair.
The value is a float representing the nominal number of picofarads.
The tolerance is a free-form string, and may be None.
'''
code_length = len(code)
if code_length == 2:
return float(code), None
elif code_length == 3:
return float(code[:2]) * 10 ** int(arg[2:]), None
elif code_length == 4:
return float(code[:2]) * 10 ** int(arg[2:3]), TOLERANCES[code[3:].upper()]


In Python, it is idiomatic to write if conditions without parentheses.

However, there is a lot of redundancy there, and not much insight either. An improvement would be:

def decode_capacitance(code):
'''(Same docstring)'''
base_pF, scale, tol = float(code[0:2]), int(code[2:3] or 0), code[3:4]
multiplier = 10 ** scale
return base_pF * multiplier, TOLERANCES.get(tol.upper())


You fail to interpret the third digit properly. If the third digit is 8 or 9, it is supposed to be interpreted as a multiplier of 0.01 or 0.1, respectively. To fix that, change the middle line to:

    multiplier = 10 ** (scale if scale <= 5 else scale - 10)


As @vnp noted, you have no validation or error handling (and I haven't added any for you).

Formatting technique

Define another function to display the capacitance and tolerance in the desired format.

It would be nice, I think, to automatically select the most natural units.

The standard symbol for the farad is F — a capital letter.

Putting it all together

With the appropriate functions defined, the main loop is quite simple. Print results as you go; there's no need to store results.

import sys

TOLERANCES = {
'B': '.10%',
'C': '.25%',
'D': '.5%',
'E': '.5%',
'F': '1%',
'G': '2%',
'H': '3%',
'J': '5%',
'K': '10%',
'M': '20%',
'N': '.05%',
'P': '+100% to 0%',
'Z': '+80% to -20%',
}

def decode_capacitance(code):
'''
Decodes the capacitance as a (value, tolerance) pair.
The value is a float representing the nominal number of picofarads.
The tolerance is a free-form string, and may be None.
'''
base_pF, scale, tol = float(code[0:2]), int(code[2:3] or 0), code[3:4]
multiplier = 10 ** int(scale if scale <= 5 else scale - 10)
return base_pF * multiplier, TOLERANCES.get(tol.upper())

def format_capacitance(pF, tolerance):
if pF > 1000000:
mantissa, unit = pF / 1000000, 'uF'
else:
mantissa, unit = pF, 'pF'

output = '{} {}'.format(mantissa, unit)
if tolerance is not None:
output += ', ' + tolerance + ' tolerance'
return output

# Skip argv[0], which is the name of this program
cmd_args = sys.argv[1:]

# Cycle through passed arguments and decode each capacitor
for arg in cmd_args:
print('{} = {}'.format(arg, format_capacitance(*decode_capacitance(arg))))

• I plan to add a error handling, a -h & --help arg to provide a help output, and a read me, i wanted to make a note of you as a contributor to the code, i also plan to study your points heavily, when i complete error handling and the help args, would it be correct to re-edit it or re-post it to get some idea if i am on the right track with it, also need to come up with a simple project to make use of your suggestion and practice them – John Oct 15 '14 at 7:55
• For iterative reviews, please post a separate question. (Thanks for asking about the process!) – 200_success Oct 15 '14 at 7:58

Usage

• The code expects a perfect input. A user making a honest mistake is faced with an exception trace. It is nice to detect an error early and print an error message in big friendly letters.

• In the same tune, I am not sure how the valid input should be formatted. It is very well possible that a professional radio engineer knows exactly what is expected; not many of us are though. The usage() function is pretty much a must.

Structure

• Any piece of code you care about should be written with a future reuse in mind. In a Python world it usually means that you'd be able to import it into another project. A habit of factoring the code into well defined functions is immensely helpful.

Misc

cmdargs = sys.argv
cmdargs = cmdargs[1:]


looks really strange. I'd go for

cmdargs = sys.argv[1:]


wrapped in a try/except clause of cource.

• Concerning your last remark, an out-of-bounds list slice is just an empty list. No exception is raised. – Janne Karila Oct 15 '14 at 6:37