/// <summary>
 /// Returns the IPythonType for a given PythonException in a specified module.
 /// Throws KeyNotFoundException it doesn't exist.
 /// </summary>
 public static IPythonType GetPythonException(string name, string module)
 {
     IPythonType type;
     QualifiedExceptionName key = new QualifiedExceptionName(name, module);
     if (nameToPython.TryGetValue(key, out type)) {
         return type;
     }
     throw new KeyNotFoundException("exception " + module + "." + name + " does not exist");
 }
        /// <summary>
        /// Creates and returns the IPythonType for a given PythonException in a given module with a given base type using a given exception creator.
        /// Throws InvalidOperationException if it already exists and has a different base type.
        /// 
        /// Note that specifying the module doesn't actually place the created type in that module.
        /// The type knows about the module, but the module doesn't know about the type. It's the caller's
        /// responsibility to put the returned type in the appropriate module.
        /// </summary>
        public static IPythonType CreatePythonException(string name, string module, IPythonType baseType, ExceptionClassCreator creator)
        {
            IPythonType type;
            QualifiedExceptionName key = new QualifiedExceptionName(name, module);
            if (nameToPython.TryGetValue(key, out type)) {
                if (Ops.Equals(type.BaseClasses, ObjectToTuple(baseType))) {
                    return type;
                } else {
                    throw new InvalidOperationException(module + "." + name + " already exists with different base type(s)");
                }
            }

            IPythonType res = creator(name, module, baseType);
            nameToPython[key] = res;
            return res;
        }