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

Code before:
            index_sample = None  # Dummy sample

        name_sample = names[0]
        if isinstance(index_sample, tuple):
            if not isinstance(name_sample, tuple):
                msg = "must supply a tuple to get_group with multiple grouping keys"
                raise ValueError(msg)
            if not len(name_sample) == len(index_sample):
                try:
                    # If the original grouper was a tuple
                    return [self.indices[name] for name in names]
                except KeyError as err:
                    # turns out it wasn't a tuple
                    msg = (
                        "must supply a same-length tuple to get_group "
                        "with multiple grouping keys"
                    )
                    raise ValueError(msg) from err

            converters = (get_converter(s) for s in index_sample)
            names = (
                tuple(f(n) for f, n in zip(converters, name, strict=True))
                for name in names
            )

        elif any(isna(k) for k in self.indices.keys()):
            converters = [get_converter(name) for name in names]
            names = (converter(name) for converter, name in zip(converters, names))

        else:
            converter = get_converter(index_sample)
            names = (converter(name) for name in names)

        indices = {np.nan if isna(k) else k: v for k, v in self.indices.items()}

        return [indices.get(name, []) for name in names]

    @final
    def _get_index(self, name):
        """
        Safe get index, translate keys for datelike to underlying repr.
        """
        return self._get_indices([name])[0]

    @final
    @cache_readonly
    def _selected_obj(self):
        # Note: _selected_obj is always just `self.obj` for SeriesGroupBy
        if isinstance(self.obj, Series):
            return self.obj


Review comment (performance):
This check is expensive - this function is only ever called currently with `names` a list of length 1, and the rest of the method is O(1) in terms of `self.indices`. It's called from the inner loop of `DataFramGroupBy.fitler` as we're iterating over each group. This seems avoidable.

I believe we could change this function to just accept a single `name` (rather than a list) and then have a special case:

```python
if isna(name):
    return self.indices.get(np.nan, [])
```

Code after:

        if isinstance(index_sample, tuple):
            if not isinstance(name, tuple):
                msg = "must supply a tuple to get_group with multiple grouping keys"
                raise ValueError(msg)
            if not len(name) == len(index_sample):
                try:
                    # If the original grouper was a tuple
                    return self.indices[name]
                except KeyError as err:
                    # turns out it wasn't a tuple
                    msg = (
                        "must supply a same-length tuple to get_group "
                        "with multiple grouping keys"
                    )
                    raise ValueError(msg) from err

            converters = (get_converter(s) for s in index_sample)
            name = tuple(f(n) for f, n in zip(converters, name, strict=True))
        else:
            converter = get_converter(index_sample)
            name = converter(name)

        return self.indices.get(name, [])

    @final
    @cache_readonly
    def _selected_obj(self):
        # Note: _selected_obj is always just `self.obj` for SeriesGroupBy
        if isinstance(self.obj, Series):
            return self.obj

        if self._selection is not None:
            if is_hashable(self._selection):
                # i.e. a single key, so selecting it will return a Series.
                #  In this case, _obj_with_exclusions would wrap the key
                #  in a list and return a single-column DataFrame.
                return self.obj[self._selection]

            # Otherwise _selection is equivalent to _selection_list, so
            #  _selected_obj matches _obj_with_exclusions, so we can reuse
            #  that and avoid making a copy.
            return self._obj_with_exclusions

        return self.obj

    @final
    def _dir_additions(self) -> set[str]:
        return self.obj._dir_additions()

    @overload