Repository: pandas-dev/pandas

Issue:
BUG: recursion limit on eval when operating on different types
Me being dumb discovered this:

``` python
In [209]: bug = pd.DataFrame({"A": [1, 2], 'B': ['c', 'd']})

In [210]: bug
Out[210]: 
   A  B
0  1  c
1  2  d

In [211]: bug.eval('A + B')

# stack trace truncated ...

RuntimeError: maximum recursion depth exceeded
```

cc @cpcloud 



Discussion:
Just now saw this


Resolving patch:
diff --git a/pandas/computation/expr.py b/pandas/computation/expr.py
--- a/pandas/computation/expr.py
+++ b/pandas/computation/expr.py
@@ -377,6 +377,11 @@ def _possibly_evaluate_binop(self, op, op_class, lhs, rhs,
                                                        '<=', '>=')):
         res = op(lhs, rhs)
 
+        if res.has_invalid_return_type:
+            raise TypeError("unsupported operand type(s) for {0}:"
+                            " '{1}' and '{2}'".format(res.op, lhs.type,
+                                                      rhs.type))
+
         if self.engine != 'pytables':
             if (res.op in _cmp_ops_syms
                     and getattr(lhs, 'is_datetime', False)
diff --git a/pandas/computation/ops.py b/pandas/computation/ops.py
--- a/pandas/computation/ops.py
+++ b/pandas/computation/ops.py
@@ -169,7 +169,7 @@ def name(self):
 
 class Op(StringMixin):
 
-    """Hold an operator of unknown arity
+    """Hold an operator of arbitrary arity
     """
 
     def __init__(self, op, operands, *args, **kwargs):
@@ -195,6 +195,16 @@ def return_type(self):
             return np.bool_
         return _result_type_many(*(term.type for term in com.flatten(self)))
 
+    @property
+    def has_invalid_return_type(self):
+        types = self.operand_types
+        obj_dtype_set = frozenset([np.dtype('object')])
+        return self.return_type == object and types - obj_dtype_set
+
+    @property
+    def operand_types(self):
+        return frozenset(term.type for term in com.flatten(self))
+
     @property
     def isscalar(self):
         return all(operand.isscalar for operand in self.operands)
@@ -412,6 +422,10 @@ def _disallow_scalar_only_bool_ops(self):
             raise NotImplementedError("cannot evaluate scalar only bool ops")
 
 
+def isnumeric(dtype):
+    return issubclass(np.dtype(dtype).type, np.number)
+
+
 class Div(BinOp):
 
     """Div operator to special case casting.
@@ -428,6 +442,12 @@ class Div(BinOp):
     def __init__(self, lhs, rhs, truediv, *args, **kwargs):
         super(Div, self).__init__('/', lhs, rhs, *args, **kwargs)
 
+        if not isnumeric(lhs.return_type) or not isnumeric(rhs.return_type):
+            raise TypeError("unsupported operand type(s) for {0}:"
+                            " '{1}' and '{2}'".format(self.op,
+                                                      lhs.return_type,
+                                                      rhs.return_type))
+
         if truediv or PY3:
             _cast_inplace(com.flatten(self), np.float_)
 


Test patch:
diff --git a/pandas/tests/test_frame.py b/pandas/tests/test_frame.py
--- a/pandas/tests/test_frame.py
+++ b/pandas/tests/test_frame.py
@@ -13243,6 +13243,16 @@ def test_bool_arith_expr(self):
         expect = self.frame.a[self.frame.a < 1] + self.frame.b
         assert_series_equal(res, expect)
 
+    def test_invalid_type_for_operator_raises(self):
+        df = DataFrame({'a': [1, 2], 'b': ['c', 'd']})
+        ops = '+', '-', '*', '/'
+        for op in ops:
+            with tm.assertRaisesRegexp(TypeError,
+                                       "unsupported operand type\(s\) for "
+                                       ".+: '.+' and '.+'"):
+                df.eval('a {0} b'.format(op), engine=self.engine,
+                        parser=self.parser)
+
 
 class TestDataFrameEvalNumExprPython(TestDataFrameEvalNumExprPandas):