Better exceptions

Uses better exception messages and types for the two failure conditions: no key found, and duplicate keys.

An outstanding issue with these changes are the types of exceptions used. For Python, `KeyError` is used for both failures, which seems fine to me. For .NET though, we don't have control over the key not found failure (the runtime throws `RuntimeBinderException`), and the best type I can come up with for duplicates is the `InvalidOperationException`. Would it be worth it to create custom exceptions for these two cases (the single case for .net)?

Another issue I have is my implementation of duplicate finding in Python. I basically have no idea what I'm doing in Python, and would love some feedback on whether there is a better and/or more idiomatic solution.