Inzynierka/Lib/site-packages/pandas/core/ops/common.py

152 lines
3.6 KiB
Python
Raw Normal View History

2023-06-02 12:51:02 +02:00
"""
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