[Tutor] Python 3: string to decimal conversion

cs at zip.com.au cs at zip.com.au
Mon May 23 21:50:03 EDT 2016


On 23May2016 12:18, Saidov <usaidov at gmail.com> wrote:
>Thanks everyone for all your help. This solved my problem with
>parenthesis and $ signs in the data:
>
>if not row[4]:
>                    pass
>                else:
>                    try:
>                        expenses[ts.Date(row[0]).month] +=
>decimal.Decimal(row[4].strip('()$ ,').replace(',',''))
>                    except decimal.InvalidOperation as e:
>                        print("unexpected expenses value:  %r" % (row[4]))

These are three things to remark on with your new code:

Firstly, it is best to put try/except around as narrow a piece of code as 
possible. In your code about, you would catch decimal.InvalidOperation from any 
of the operations, including the strip and .replace operations. While it 
happens that these do not raise that exception, the problem is that when an 
exception is raised you have a much vaguer idea of where it came from, 
especially with more common exceptions like ValueError.

Therefore I would suggest reshaping the above like this:

    numeric_expense = row[4].strip('()$ ,').replace(',', '')
    try:
        expense = decimal.Decimal(numeric_expense)
    except decimal.InvalidOperation as e:
        print("unexpected numeric expsense: %r (%s)" % (numeric_expense, e))
    else:
        expenses[ts.Date(row[0]).month] += expense

In this way you are watching for _only_ the decimal.Decimal() call, not the 
other methods. Also, it makes it easy to report the actual string that caused 
the trouble (numeric_expense) because you have now pulled it out and given it a 
nice name. This means that your note reporting an error about row[4] _before_ 
it got cleaned up, you're reporting about how it was after the cleanup. Also, 
we're printing the exception itself in the message as well as the string: here 
it may not help more, but with many exceptions there will be further detail 
which can be helpful, so it is good practice to print the exception (e).

Second, error messages should go to the program's error output, which is a 
separate output stream from stdout. So the print should look like this:

    print(....., file=sys.stderr)

When it is all going to your terminal you won't see the f=difference, but as 
soon as your program is run like this:

    python my-program.py >report.txt

you will get the "report" information in your file and the error output will 
still show up on your terminal where you will see it.

Thirdly, your .strip('()') is throwing away the brackets around the expense. As 
Alan pointed out, it is common convention in accounting to express negaitve 
values (losses, withdrawals, etc) in brakcets. So this "4.00" means 4.00 and 
this "(4.00)" means -4.00. And in consequence you should probably be 
_subtracting_ any value in brackets instead of adding it. This is why ALan had 
a distinct if-statement to recognise the brackets. Example, untested:

    if row[4].startswith('(') and row[4].endswith(')'):
        row[4] = row[4][1:-1]
        negative = True
    else:
        negative = False
    # ... string $ sign and commas, convert to Decimal ...
    try:  ....
    except ....:
    else:
        if negative:
            expense = -expense
        expenses[ts.Date(row[0]).month] += expense

As your code current is, it is adding all the negative values, which may not be 
what is needed.

Cheers,
Cameron Simpson <cs at zip.com.au>


More information about the Tutor mailing list