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

Code before:
        case: bool = True,
        flags: int = 0,
        na=lib.no_default,
        regex: bool = True,
    ):
        if flags or self._is_re_pattern_with_flags(pat):
            return super()._str_contains(pat, case, flags, na, regex)
        if isinstance(pat, re.Pattern):
            # TODO flags passed separately by user are ignored
            pat, case, flags = self._preprocess_re_pattern(pat, case)

        return ArrowStringArrayMixin._str_contains(self, pat, case, flags, na, regex)

    def _str_match(
        self,
        pat: str | re.Pattern,
        case: bool = True,
        flags: int = 0,
        na: Scalar | lib.NoDefault = lib.no_default,
    ):
        if flags or self._is_re_pattern_with_flags(pat):
            return super()._str_match(pat, case, flags, na)
        if isinstance(pat, re.Pattern):
            pat, case, flags = self._preprocess_re_pattern(pat, case)

        if pat.endswith("\\Z") and not pat.endswith("\\\\Z"):
            pat = pat[:-2] + "\\z"

        return ArrowStringArrayMixin._str_match(self, pat, case, flags, na)

    def _str_fullmatch(
        self,
        pat: str | re.Pattern,
        case: bool = True,
        flags: int = 0,
        na: Scalar | lib.NoDefault = lib.no_default,
    ):
        if flags or self._is_re_pattern_with_flags(pat):
            return super()._str_fullmatch(pat, case, flags, na)
        if isinstance(pat, re.Pattern):
            pat, case, flags = self._preprocess_re_pattern(pat, case)

        return ArrowStringArrayMixin._str_fullmatch(self, pat, case, flags, na)

    def _str_replace(
        self,
        pat: str | re.Pattern,
        repl: str | Callable,
        n: int = -1,
        case: bool = True,
        flags: int = 0,

Review comment (suggestion):
This correctly handles what I think would be the vast majority of real cases and as far as I can tell has no false positives. However it does have false negatives. The following should give true:

- `r"\\\Z"` (in general, an odd number of `\`)
- `r"text(\Z)`
- `r"\Z text"`

Only the first seems like it could maybe occur in practice, the other two are likely erroneous. And while I could see this being used with lookarounds, we'll already be falling back to object dtype with those so we don't need to be worried about them here.

Code after:
            pattern = pat

        if (
            pattern.endswith("\\Z")
            # Second condition counts the number of `\` that patterns ends with
            # prior to Z -> needs to be odd to end with an unescaped \Z
            and (len(pattern) - len(pattern[:-1].rstrip("\\")) + 1) % 2 == 1
        ):
            pattern = pattern[:-2] + "\\z"

        return pattern, case, flags

    def _str_contains(
        self,
        pat,
        case: bool = True,
        flags: int = 0,
        na=lib.no_default,
        regex: bool = True,
    ):
        if (
            flags
            or self._is_re_pattern_with_flags(pat)
            or (regex and self._has_unsupported_regex(pat))
        ):
            return super()._str_contains(pat, case, flags, na, regex)

        pat, case, flags = self._preprocess_re_pattern(pat, case, flags)
        return ArrowStringArrayMixin._str_contains(self, pat, case, flags, na, regex)

    def _str_match(
        self,
        pat: str | re.Pattern,
        case: bool = True,
        flags: int = 0,
        na: Scalar | lib.NoDefault = lib.no_default,
    ):
        if (
            flags
            or self._is_re_pattern_with_flags(pat)
            or self._has_unsupported_regex(pat)
        ):
            return super()._str_match(pat, case, flags, na)

        pat, case, flags = self._preprocess_re_pattern(pat, case, flags)
        return ArrowStringArrayMixin._str_match(self, pat, case, flags, na)

    def _str_fullmatch(
        self,
        pat: str | re.Pattern,
        case: bool = True,