Repository: pandas-dev/pandas
File: pandas/core/generic.py (Python)

Code before:
            else:
                raise TypeError(
                    '"value" parameter must be a scalar, dict '
                    "or Series, but you passed a "
                    f'"{type(value).__name__}"'
                )

            new_data = self._mgr.fillna(value=value, limit=limit, inplace=inplace)

        elif isinstance(value, (dict, ABCSeries)):
            result = self if inplace else self.copy(deep=False)
            if axis == 1:
                # Check that all columns in result have the same dtype
                # otherwise don't bother with ffill and losing accurate dtypes
                dtypes = [result[col].dtype for col in result.columns]
                if len(set(dtypes)) > 1:
                    raise ValueError(
                        "All columns must have the same dtype, but got dtypes: "
                        f"{dict(zip(result.columns, dtypes))}"
                    )
                if (value_dtype := np.asarray(value).dtype) != dtypes[0]:
                    raise ValueError(
                        "Dtype mismatch for value "
                        f"(value.dtype={value_dtype} vs {dtypes[0]})"
                    )
                for i, _ in enumerate(result):
                    # rowise update by index in case of duplicate column names
                    result.iloc[:, i] = result.iloc[:, i].fillna(
                        value=value, limit=limit
                    )
            else:
                for k, v in value.items():
                    if k not in result:
                        continue

                    res_k = result[k].fillna(v, limit=limit)

                    if not inplace:
                        result[k] = res_k
                    else:
                        # We can write into our existing column(s) iff dtype
                        #  was preserved.
                        if isinstance(res_k, ABCSeries):
                            # i.e. 'k' only shows up once in self.columns
                            if res_k.dtype == result[k].dtype:
                                result.loc[:, k] = res_k
                            else:
                                # Different dtype -> no way to do inplace.
                                result[k] = res_k
                        else:
                            # see test_fillna_dict_inplace_nonunique_columns

Review comment (performance):
This type of enumeration is likely very slow. Generally our axis=1 operations are slower so we don't need to match axis=0, but I think we can do better.

Perhaps we can get away with transposing first, then applying the fill, then transposing back to the original shape?

@mroeschke maybe has some ideas too

Code after:
                pass
            else:
                raise TypeError(
                    '"value" parameter must be a scalar, dict '
                    "or Series, but you passed a "
                    f'"{type(value).__name__}"'
                )

            new_data = self._mgr.fillna(value=value, limit=limit, inplace=inplace)

        elif isinstance(value, (dict, ABCSeries)):
            result = self if inplace else self.copy(deep=False)
            if axis == 1:
                # Check that all columns in result have the same dtype
                # otherwise don't bother with fillna and losing accurate dtypes
                unique_dtypes = algos.unique(self._mgr.get_dtypes())
                if len(unique_dtypes) > 1:
                    raise ValueError(
                        "All columns must have the same dtype, but got dtypes: "
                        f"{list(unique_dtypes)}"
                    )
                # Use the first column, which we have already validated has the
                # same dtypes as the other columns.
                if not can_hold_element(result.iloc[:, 0], value):
                    frame_dtype = unique_dtypes.item()
                    raise ValueError(
                        f"{value} not a suitable type to fill into {frame_dtype}"
                    )
                result = result.T.fillna(value=value).T
            else:
                for k, v in value.items():
                    if k not in result:
                        continue

                    res_k = result[k].fillna(v, limit=limit)

                    if not inplace:
                        result[k] = res_k
                    else:
                        # We can write into our existing column(s) iff dtype
                        #  was preserved.
                        if isinstance(res_k, ABCSeries):
                            # i.e. 'k' only shows up once in self.columns
                            if res_k.dtype == result[k].dtype:
                                result.loc[:, k] = res_k
                            else:
                                # Different dtype -> no way to do inplace.
                                result[k] = res_k
                        else:
                            # see test_fillna_dict_inplace_nonunique_columns
                            locs = result.columns.get_loc(k)