# Buggy code
def reset_index(self, level=None, drop=False, inplace=False, col_level=0, col_fill=""):
    """
    For DataFrame with multi-level index, return new DataFrame with
    labeling information in the columns under the index names, defaulting
    to 'level_0', 'level_1', etc. if any are None. For a standard index,
    the index name will be used (if set), otherwise a default 'index' or
    'level_0' (if 'index' is already taken) will be used.

    Parameters
    ----------
    level : int, str, tuple, or list, default None
        Only remove the given levels from the index. Removes all levels by
        default
    drop : boolean, default False
        Do not try to insert index into dataframe columns. This resets
        the index to the default integer index.
    inplace : boolean, default False
        Modify the DataFrame in place (do not create a new object)
    col_level : int or str, default 0
        If the columns have multiple levels, determines which level the
        labels are inserted into. By default it is inserted into the first
        level.
    col_fill : object, default ''
        If the columns have multiple levels, determines how the other
        levels are named. If None then the index name is repeated.

    Returns
    -------
    resetted : DataFrame
    """
    inplace = validate_bool_kwarg(inplace, "inplace")
    if inplace:
        new_obj = self
    else:
        new_obj = self.copy()

    def _maybe_casted_values(index, labels=None):
        if isinstance(index, PeriodIndex):
            values = index.asobject.values
        elif isinstance(index, DatetimeIndex) and index.tz is not None:
            values = index
        else:
            values = index.values
            if values.dtype == np.object_:
                values = lib.maybe_convert_objects(values)

        # if we have the labels, extract the values with a mask
        if labels is not None:
            mask = labels == -1

            # we can have situations where the whole mask is -1,
            # meaning there is nothing found in labels, so make all nan's
            if mask.all():
                values = np.empty(len(mask))
                values.fill(np.nan)
            else:
                values = values.take(labels)
                if mask.any():
                    values, changed = maybe_upcast_putmask(values, mask, np.nan)
        return values

    new_index = _default_index(len(new_obj))
    if isinstance(self.index, MultiIndex):
        if level is not None:
            if not isinstance(level, (tuple, list)):
                level = [level]
            level = [self.index._get_level_number(lev) for lev in level]
            if len(level) < len(self.index.levels):
                new_index = self.index.droplevel(level)

        if not drop:
            names = self.index.names
            zipped = lzip(self.index.levels, self.index.labels)

            multi_col = isinstance(self.columns, MultiIndex)
            for i, (lev, lab) in reversed(list(enumerate(zipped))):
                col_name = names[i]
                if col_name is None:
                    col_name = "level_%d" % i

                if multi_col:
                    if col_fill is None:
                        col_name = tuple([col_name] * self.columns.nlevels)
                    else:
                        name_lst = [col_fill] * self.columns.nlevels
                        lev_num = self.columns._get_level_number(col_level)
                        name_lst[lev_num] = col_name
                        col_name = tuple(name_lst)

                # to ndarray and maybe infer different dtype
                level_values = _maybe_casted_values(lev, lab)
                if level is None or i in level:
                    new_obj.insert(0, col_name, level_values)

    elif not drop:
        name = self.index.name
        if name is None or name == "index":
            name = "index" if "index" not in self else "level_0"
        if isinstance(self.columns, MultiIndex):
            if col_fill is None:
                name = tuple([name] * self.columns.nlevels)
            else:
                name_lst = [col_fill] * self.columns.nlevels
                lev_num = self.columns._get_level_number(col_level)
                name_lst[lev_num] = name
                name = tuple(name_lst)
        values = _maybe_casted_values(self.index)
        new_obj.insert(0, name, values)

    new_obj.index = new_index
    if not inplace:
        return new_obj


# Traceback (TypeError)
In [2]: df = pd.DataFrame([[1, 2]], columns=pd.MultiIndex.from_product([['A'], ['a', 'b']]))

In [3]: reind = df.set_index([('A', 'a')])

In [4]: reind.reset_index()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/home/pietro/nobackup/repo/pandas/pandas/util/hashing.py in hash_array(vals, encoding, hash_key, categorize)
265         try:
--> 266             vals = _hash.hash_object_array(vals, hash_key, encoding)
267         except TypeError:

as/pandas/util/hashing.pyx in pandas.util.libhashing.hash_object_array (pandas/util/hashing.c:2372)()

TypeError: ('A', 'a') of type <class 'tuple'> is not a valid type for hashing, must be string or null

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
<ipython-input-4-985862d3b27d> in <module>()
----> 1 reind.reset_index()

/home/pietro/nobackup/repo/pandas/pandas/core/frame.py in reset_index(self, level, drop, inplace, col_level, col_fill)
3057                     name = tuple(name_lst)
3058             values = _maybe_casted_values(self.index)
-> 3059             new_obj.insert(0, name, values)
3060
3061         new_obj.index = new_index

/home/pietro/nobackup/repo/pandas/pandas/core/frame.py in insert(self, loc, column, value, allow_duplicates)
2518         value = self._sanitize_column(column, value, broadcast=False)
2519         self._data.insert(loc, column, value,
-> 2520                           allow_duplicates=allow_duplicates)
2521
2522     def assign(self, **kwargs):

/home/pietro/nobackup/repo/pandas/pandas/core/internals.py in insert(self, loc, item, value, allow_duplicates)
3806
3807         """
-> 3808         if not allow_duplicates and item in self.items:
3809             # Should this be a different kind of error??
3810             raise ValueError('cannot insert {}, already exists'.format(item))

/home/pietro/nobackup/repo/pandas/pandas/core/indexes/multi.py in __contains__(self, key)
1326         hash(key)
1327         try:
-> 1328             self.get_loc(key)
1329             return True
1330         except LookupError:

/home/pietro/nobackup/repo/pandas/pandas/core/indexes/multi.py in get_loc(self, key, method)
1984             key = _values_from_object(key)
1985             key = tuple(map(_maybe_str_to_time_stamp, key, self.levels))
-> 1986             return self._engine.get_loc(key)
1987
1988         # -- partial selection or non-unique index

/home/pietro/nobackup/repo/pandas/pandas/_libs/index.pyx in pandas._libs.index.MultiIndexEngine.get_loc (pandas/_libs/index.c:13171)()

/home/pietro/nobackup/repo/pandas/pandas/_libs/index.pyx in pandas._libs.index.MultiIndexEngine.get_loc (pandas/_libs/index.c:13018)()

/home/pietro/nobackup/repo/pandas/pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.MultiIndexHashTable.get_item (pandas/_libs/hashtable.c:23625)()

/home/pietro/nobackup/repo/pandas/pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.MultiIndexHashTable.get_item (pandas/_libs/hashtable.c:23374)()

/home/pietro/nobackup/repo/pandas/pandas/core/indexes/multi.py in _hashed_indexing_key(self, key)
755         key = tuple([f(k, stringify)
756                      for k, stringify in zip(key, self._have_mixed_levels)])
--> 757         return hash_tuples(key)
758
759     @Appender(base._shared_docs['duplicated'] % _index_doc_kwargs)

/home/pietro/nobackup/repo/pandas/pandas/util/hashing.py in hash_tuples(vals, encoding, hash_key)
159                                 hash_key=hash_key)
160               for cat in vals)
--> 161     h = _combine_hash_arrays(hashes, len(vals))
162     if is_tuple:
163         h = h[0]

/home/pietro/nobackup/repo/pandas/pandas/util/hashing.py in _combine_hash_arrays(arrays, num_items)
31     """
32     try:
---> 33         first = next(arrays)
34     except StopIteration:
35         return np.array([], dtype=np.uint64)

/home/pietro/nobackup/repo/pandas/pandas/util/hashing.py in <genexpr>(.0)
158                                 encoding=encoding,
159                                 hash_key=hash_key)
--> 160               for cat in vals)
161     h = _combine_hash_arrays(hashes, len(vals))
162     if is_tuple:

/home/pietro/nobackup/repo/pandas/pandas/util/hashing.py in _hash_categorical(c, encoding, hash_key)
182     """
183     hashed = hash_array(c.categories.values, encoding, hash_key,
--> 184                         categorize=False)
185
186     # we have uint64, as we don't directly support missing values

/home/pietro/nobackup/repo/pandas/pandas/util/hashing.py in hash_array(vals, encoding, hash_key, categorize)
267         except TypeError:
268             # we have mixed types
--> 269             vals = _hash.hash_object_array(vals.astype(str).astype(object),
270                                            hash_key, encoding)
271

ValueError: setting an array element with a sequence

# Fixed code
def reset_index(self, level=None, drop=False, inplace=False, col_level=0, col_fill=""):
    """
    For DataFrame with multi-level index, return new DataFrame with
    labeling information in the columns under the index names, defaulting
    to 'level_0', 'level_1', etc. if any are None. For a standard index,
    the index name will be used (if set), otherwise a default 'index' or
    'level_0' (if 'index' is already taken) will be used.

    Parameters
    ----------
    level : int, str, tuple, or list, default None
        Only remove the given levels from the index. Removes all levels by
        default
    drop : boolean, default False
        Do not try to insert index into dataframe columns. This resets
        the index to the default integer index.
    inplace : boolean, default False
        Modify the DataFrame in place (do not create a new object)
    col_level : int or str, default 0
        If the columns have multiple levels, determines which level the
        labels are inserted into. By default it is inserted into the first
        level.
    col_fill : object, default ''
        If the columns have multiple levels, determines how the other
        levels are named. If None then the index name is repeated.

    Returns
    -------
    resetted : DataFrame
    """
    inplace = validate_bool_kwarg(inplace, "inplace")
    if inplace:
        new_obj = self
    else:
        new_obj = self.copy()

    def _maybe_casted_values(index, labels=None):
        if isinstance(index, PeriodIndex):
            values = index.asobject.values
        elif isinstance(index, DatetimeIndex) and index.tz is not None:
            values = index
        else:
            values = index.values
            if values.dtype == np.object_:
                values = lib.maybe_convert_objects(values)

        # if we have the labels, extract the values with a mask
        if labels is not None:
            mask = labels == -1

            # we can have situations where the whole mask is -1,
            # meaning there is nothing found in labels, so make all nan's
            if mask.all():
                values = np.empty(len(mask))
                values.fill(np.nan)
            else:
                values = values.take(labels)
                if mask.any():
                    values, changed = maybe_upcast_putmask(values, mask, np.nan)
        return values

    new_index = _default_index(len(new_obj))
    if isinstance(self.index, MultiIndex):
        if level is not None:
            if not isinstance(level, (tuple, list)):
                level = [level]
            level = [self.index._get_level_number(lev) for lev in level]
            if len(level) < len(self.index.levels):
                new_index = self.index.droplevel(level)

    if not drop:
        if isinstance(self.index, MultiIndex):
            names = [
                n if n is not None else ("level_%d" % i)
                for (i, n) in enumerate(self.index.names)
            ]
            to_insert = lzip(self.index.levels, self.index.labels)
        else:
            default = "index" if "index" not in self else "level_0"
            names = [default] if self.index.name is None else [self.index.name]
            to_insert = ((self.index, None),)

        multi_col = isinstance(self.columns, MultiIndex)
        for i, (lev, lab) in reversed(list(enumerate(to_insert))):
            name = names[i]
            if multi_col:
                col_name = list(name) if isinstance(name, tuple) else [name]
                if col_fill is None:
                    if len(col_name) not in (1, self.columns.nlevels):
                        raise ValueError(
                            "col_fill=None is incompatible "
                            "with incomplete column name "
                            "{}".format(name)
                        )
                    col_fill = col_name[0]

                lev_num = self.columns._get_level_number(col_level)
                name_lst = [col_fill] * lev_num + col_name
                missing = self.columns.nlevels - len(name_lst)
                name_lst += [col_fill] * missing
                name = tuple(name_lst)

            # to ndarray and maybe infer different dtype
            level_values = _maybe_casted_values(lev, lab)
            if level is None or i in level:
                new_obj.insert(0, name, level_values)

    new_obj.index = new_index
    if not inplace:
        return new_obj