\[ [Index](index.md) | [Exercise 5.4](ex5_4.md) | [Exercise 5.6](ex5_6.md) \]

# Exercise 5.5

*Objectives:*

- Learn more about exception handling and logging

*Files Modified:* `reader.py`

In the `reader.py` file, there is a central function `convert_csv()` that does
most of the work.  This function crashes if you run it on data with missing or
bad data.  For example:

```python
>>> port = read_csv_as_dicts('Data/missing.csv', types=[str, int, float])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "reader.py", line 24, in read_csv_as_dicts
    return csv_as_dicts(file, types, headers=headers)
  File "reader.py", line 13, in csv_as_dicts
    lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) })
  File "reader.py", line 9, in convert_csv
    return list(map(lambda row: converter(headers, row), rows))
  File "reader.py", line 9, in <lambda>
    return list(map(lambda row: converter(headers, row), rows))
  File "reader.py", line 13, in <lambda>
    lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) })
  File "reader.py", line 13, in <dictcomp>
    lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) })
ValueError: invalid literal for int() with base 10: ''
>>> 
```

## (a) Catching Exceptions

Instead of crashing on bad data, modify the code to issue a warning message
instead. The final result should be a list of the rows that were successfully
converted. For example:

```python
>>> port = read_csv_as_dicts('Data/missing.csv', types=[str, int, float])
Row 4: Bad row: ['C', '', '53.08']
Row 7: Bad row: ['DIS', '50', 'N/A']
Row 8: Bad row: ['GE', '', '37.23']
Row 13: Bad row: ['INTC', '', '21.84']
Row 17: Bad row: ['MCD', '', '51.11']
Row 19: Bad row: ['MO', '', '70.09']
Row 22: Bad row: ['PFE', '', '26.40']
Row 26: Bad row: ['VZ', '', '42.92']
>>> len(port)
20
>>>
```

Note:  Making this change may be a bit tricky because of your previous use of the `map()`
built-in function. You may have to abandon that approach since there's no easy way to catch
and handle exceptions in `map()`.

## (b) Logging

Modify the code so that warning messages are issued using the `logging` module.  In 
addition, give optional debugging information indicating the reason for failure.
For example:

```python
>>> import reader
>>> import logging
>>> logging.basicConfig(level=logging.DEBUG)
>>> port = reader.read_csv_as_dicts('Data/missing.csv', types=[str, int, float])
WARNING:reader:Row 4: Bad row: ['C', '', '53.08']
DEBUG:reader:Row 4: Reason: invalid literal for int() with base 10: ''
WARNING:reader:Row 7: Bad row: ['DIS', '50', 'N/A']
DEBUG:reader:Row 7: Reason: could not convert string to float: 'N/A'
WARNING:reader:Row 8: Bad row: ['GE', '', '37.23']
DEBUG:reader:Row 8: Reason: invalid literal for int() with base 10: ''
WARNING:reader:Row 13: Bad row: ['INTC', '', '21.84']
DEBUG:reader:Row 13: Reason: invalid literal for int() with base 10: ''
WARNING:reader:Row 17: Bad row: ['MCD', '', '51.11']
DEBUG:reader:Row 17: Reason: invalid literal for int() with base 10: ''
WARNING:reader:Row 19: Bad row: ['MO', '', '70.09']
DEBUG:reader:Row 19: Reason: invalid literal for int() with base 10: ''
WARNING:reader:Row 22: Bad row: ['PFE', '', '26.40']
DEBUG:reader:Row 22: Reason: invalid literal for int() with base 10: ''
WARNING:reader:Row 26: Bad row: ['VZ', '', '42.92']
DEBUG:reader:Row 26: Reason: invalid literal for int() with base 10: ''
>>>
```

\[ [Solution](soln5_5.md) | [Index](index.md) | [Exercise 5.4](ex5_4.md) | [Exercise 5.6](ex5_6.md) \]

----
`>>>` Advanced Python Mastery  
`...` A course by [dabeaz](https://www.dabeaz.com)  
`...` Copyright 2007-2023  

![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)
