Repository: pandas-dev/pandas

Issue:
BUG(?)/API: pd.NA + bytes raises TypeError
```
In [1]: pd.NA + "a"
Out[1]: <NA>

In [2]: pd.NA + b"a"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [2], in <cell line: 1>()
----> 1 pd.NA + b"a"

TypeError: unsupported operand type(s) for +: 'NAType' and 'bytes'
```

Unless I'm missing something, shouldn't `In [2]` return `pd.NA`?


Discussion:

Patch:
diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst
index 2469b8191c175..16271be2e24e9 100644
--- a/doc/source/whatsnew/v2.0.0.rst
+++ b/doc/source/whatsnew/v2.0.0.rst
@@ -230,6 +230,7 @@ Missing
 ^^^^^^^
 - Bug in :meth:`Index.equals` raising ``TypeError`` when :class:`Index` consists of tuples that contain ``NA`` (:issue:`48446`)
 - Bug in :meth:`Series.map` caused incorrect result when data has NaNs and defaultdict mapping was used (:issue:`48813`)
+- Bug in :class:`NA` raising a ``TypeError`` instead of return :class:`NA` when performing a binary operation with a ``bytes`` object (:issue:`49108`)
 
 MultiIndex
 ^^^^^^^^^^
diff --git a/pandas/_libs/missing.pyx b/pandas/_libs/missing.pyx
index 518f17d840dd5..b32061fbca0e2 100644
--- a/pandas/_libs/missing.pyx
+++ b/pandas/_libs/missing.pyx
@@ -341,7 +341,7 @@ def is_numeric_na(values: ndarray) -> ndarray:
 def _create_binary_propagating_op(name, is_divmod=False):
 
     def method(self, other):
-        if (other is C_NA or isinstance(other, str)
+        if (other is C_NA or isinstance(other, (str, bytes))
                 or isinstance(other, (numbers.Number, np.bool_))
                 or util.is_array(other) and not other.shape):
             # Need the other.shape clause to handle NumPy scalars,


Test patch:
diff --git a/pandas/tests/scalar/test_na_scalar.py b/pandas/tests/scalar/test_na_scalar.py
index 88b06ec578905..a77316cbc0ea6 100644
--- a/pandas/tests/scalar/test_na_scalar.py
+++ b/pandas/tests/scalar/test_na_scalar.py
@@ -49,36 +49,29 @@ def test_hashable():
     assert d[NA] == "test"
 
 
-def test_arithmetic_ops(all_arithmetic_functions):
+@pytest.mark.parametrize(
+    "other", [NA, 1, 1.0, "a", b"a", np.int64(1), np.nan], ids=repr
+)
+def test_arithmetic_ops(all_arithmetic_functions, other):
     op = all_arithmetic_functions
 
-    for other in [NA, 1, 1.0, "a", np.int64(1), np.nan]:
-        if op.__name__ in ("pow", "rpow", "rmod") and isinstance(other, str):
-            continue
-        if op.__name__ in ("divmod", "rdivmod"):
-            assert op(NA, other) is (NA, NA)
-        else:
-            if op.__name__ == "rpow":
-                # avoid special case
-                other += 1
-            assert op(NA, other) is NA
-
-
-def test_comparison_ops():
-
-    for other in [NA, 1, 1.0, "a", np.int64(1), np.nan, np.bool_(True)]:
-        assert (NA == other) is NA
-        assert (NA != other) is NA
-        assert (NA > other) is NA
-        assert (NA >= other) is NA
-        assert (NA < other) is NA
-        assert (NA <= other) is NA
-        assert (other == NA) is NA
-        assert (other != NA) is NA
-        assert (other > NA) is NA
-        assert (other >= NA) is NA
-        assert (other < NA) is NA
-        assert (other <= NA) is NA
+    if op.__name__ in ("pow", "rpow", "rmod") and isinstance(other, (str, bytes)):
+        pytest.skip(reason=f"{op.__name__} with NA and {other} not defined.")
+    if op.__name__ in ("divmod", "rdivmod"):
+        assert op(NA, other) is (NA, NA)
+    else:
+        if op.__name__ == "rpow":
+            # avoid special case
+            other += 1
+        assert op(NA, other) is NA
+
+
+@pytest.mark.parametrize(
+    "other", [NA, 1, 1.0, "a", b"a", np.int64(1), np.nan, np.bool_(True)]
+)
+def test_comparison_ops(comparison_op, other):
+    assert comparison_op(NA, other) is NA
+    assert comparison_op(other, NA) is NA
 
 
 @pytest.mark.parametrize(