123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- """
- Boilerplate functions used in defining binary operations.
- """
- from __future__ import annotations
- from functools import wraps
- import sys
- from typing import Callable
- from pandas._libs.lib import item_from_zerodim
- from pandas._libs.missing import is_matching_na
- from pandas._typing import F
- from pandas.core.dtypes.generic import (
- ABCDataFrame,
- ABCIndex,
- ABCSeries,
- )
- def unpack_zerodim_and_defer(name: str) -> Callable[[F], F]:
- """
- Boilerplate for pandas conventions in arithmetic and comparison methods.
- Parameters
- ----------
- name : str
- Returns
- -------
- decorator
- """
- def wrapper(method: F) -> F:
- return _unpack_zerodim_and_defer(method, name)
- return wrapper
- def _unpack_zerodim_and_defer(method, name: str):
- """
- Boilerplate for pandas conventions in arithmetic and comparison methods.
- Ensure method returns NotImplemented when operating against "senior"
- classes. Ensure zero-dimensional ndarrays are always unpacked.
- Parameters
- ----------
- method : binary method
- name : str
- Returns
- -------
- method
- """
- if sys.version_info < (3, 9):
- from pandas.util._str_methods import (
- removeprefix,
- removesuffix,
- )
- stripped_name = removesuffix(removeprefix(name, "__"), "__")
- else:
- stripped_name = name.removeprefix("__").removesuffix("__")
- is_cmp = stripped_name in {"eq", "ne", "lt", "le", "gt", "ge"}
- @wraps(method)
- def new_method(self, other):
- if is_cmp and isinstance(self, ABCIndex) and isinstance(other, ABCSeries):
- # For comparison ops, Index does *not* defer to Series
- pass
- else:
- for cls in [ABCDataFrame, ABCSeries, ABCIndex]:
- if isinstance(self, cls):
- break
- if isinstance(other, cls):
- return NotImplemented
- other = item_from_zerodim(other)
- return method(self, other)
- return new_method
- def get_op_result_name(left, right):
- """
- Find the appropriate name to pin to an operation result. This result
- should always be either an Index or a Series.
- Parameters
- ----------
- left : {Series, Index}
- right : object
- Returns
- -------
- name : object
- Usually a string
- """
- if isinstance(right, (ABCSeries, ABCIndex)):
- name = _maybe_match_name(left, right)
- else:
- name = left.name
- return name
- def _maybe_match_name(a, b):
- """
- Try to find a name to attach to the result of an operation between
- a and b. If only one of these has a `name` attribute, return that
- name. Otherwise return a consensus name if they match or None if
- they have different names.
- Parameters
- ----------
- a : object
- b : object
- Returns
- -------
- name : str or None
- See Also
- --------
- pandas.core.common.consensus_name_attr
- """
- a_has = hasattr(a, "name")
- b_has = hasattr(b, "name")
- if a_has and b_has:
- try:
- if a.name == b.name:
- return a.name
- elif is_matching_na(a.name, b.name):
- # e.g. both are np.nan
- return a.name
- else:
- return None
- except TypeError:
- # pd.NA
- if is_matching_na(a.name, b.name):
- return a.name
- return None
- except ValueError:
- # e.g. np.int64(1) vs (np.int64(1), np.int64(2))
- return None
- elif a_has:
- return a.name
- elif b_has:
- return b.name
- return None
|