Repository: blaze/blaze

Issue:
BUG: fields that confict with method names not accessible via __getitem__
This is post 0.8.2 (but behind master)

```
In [8]: t = blaze.Data(DataFrame({'isin' : [1,2,3], 'max' : [4,5,6]}))

# ok that this fails
In [9]: t.max.max()
TypeError: 'Field' object is not callable

In [10]: t['max']
Out[10]: 
   max
0    4
1    5
2    6

# this should work
In [11]: t['max'].max()
TypeError: 'Field' object is not callable

In [12]: t['isin'].max()
Out[12]: 3

# this too
In [13]: t['isin'].isin([1])
TypeError: 'Field' object is not callable
```



Discussion:

Resolving patch:
diff --git a/blaze/expr/expressions.py b/blaze/expr/expressions.py
--- a/blaze/expr/expressions.py
+++ b/blaze/expr/expressions.py
@@ -169,22 +169,25 @@ def __getattr__(self, key):
         except AttributeError:
             fields = dict(zip(map(valid_identifier, self.fields),
                               self.fields))
-            if self.fields and key in fields:
+
+            # prefer the method if there's a field with the same name
+            methods = toolz.merge(
+                schema_methods(self.dshape.measure),
+                dshape_methods(self.dshape)
+            )
+            if key in methods:
+                func = methods[key]
+                if func in method_properties:
+                    result = func(self)
+                else:
+                    result = boundmethod(func, self)
+            elif self.fields and key in fields:
                 if isscalar(self.dshape.measure):  # t.foo.foo is t.foo
                     result = self
                 else:
                     result = self[fields[key]]
             else:
-                d = toolz.merge(schema_methods(self.dshape.measure),
-                                dshape_methods(self.dshape))
-                if key in d:
-                    func = d[key]
-                    if func in method_properties:
-                        result = func(self)
-                    else:
-                        result = boundmethod(func, self)
-                else:
-                    raise
+                raise
         _attr_cache[(self, key)] = result
         return result
 
diff --git a/blaze/expr/split.py b/blaze/expr/split.py
--- a/blaze/expr/split.py
+++ b/blaze/expr/split.py
@@ -46,7 +46,7 @@
 from .core import *
 from .expressions import *
 from .strings import Like
-from .expressions import ndim, shape
+from .expressions import shape
 from .math import sqrt
 from .reductions import *
 from .split_apply_combine import *
@@ -163,7 +163,7 @@ def _split_chunk(expr, leaf=None, chunk=None, keepdims=True):
 @dispatch(mean)
 def _split_agg(expr, leaf=None, agg=None):
     total = agg.total.sum(axis=expr.axis, keepdims=expr.keepdims)
-    count = agg.count.sum(axis=expr.axis, keepdims=expr.keepdims)
+    count = agg['count'].sum(axis=expr.axis, keepdims=expr.keepdims)
 
     return total / count
 


Test patch:
diff --git a/blaze/compute/tests/test_numpy_compute.py b/blaze/compute/tests/test_numpy_compute.py
--- a/blaze/compute/tests/test_numpy_compute.py
+++ b/blaze/compute/tests/test_numpy_compute.py
@@ -413,7 +413,7 @@ def test_mixed_types():
                  dtype=[('count', '<i4'), ('total', '<i8')])
     aggregate = symbol('aggregate', discover(x))
     result = compute(aggregate.total.sum(axis=(0,)) /
-                     aggregate.count.sum(axis=(0,)), x)
+                     aggregate['count'].sum(axis=(0,)), x)
     expected = (x['total'].sum(axis=0, keepdims=True) /
                 x['count'].sum(axis=0, keepdims=True)).squeeze()
     np.testing.assert_array_equal(result, expected)
diff --git a/blaze/expr/tests/test_expr.py b/blaze/expr/tests/test_expr.py
--- a/blaze/expr/tests/test_expr.py
+++ b/blaze/expr/tests/test_expr.py
@@ -1,5 +1,7 @@
 from __future__ import absolute_import, division, print_function
 
+import types
+
 import pandas as pd
 
 import pytest
@@ -165,3 +167,17 @@ def test_coerce_record():
     s = symbol('s', 'var * {a: int64, b: float64}')
     expr = s.coerce('{a: float64, b: float32}')
     assert str(expr) == "s.coerce(to='{a: float64, b: float32}')"
+
+
+def test_method_before_name():
+    t = symbol('t', 'var * {isin: int64, max: float64, count: int64}')
+    assert isinstance(t['isin'], Field)
+    assert isinstance(t['max'], Field)
+    assert isinstance(t.max, Field)
+    assert isinstance(t.isin, Field)
+    assert isinstance(t['isin'].isin, types.MethodType)
+    assert isinstance(t['max'].max, types.MethodType)
+    assert isinstance(t.max.max, types.MethodType)
+    assert isinstance(t.isin.isin, types.MethodType)
+    with pytest.raises(AttributeError):
+        t.count.max()
diff --git a/blaze/expr/tests/test_split.py b/blaze/expr/tests/test_split.py
--- a/blaze/expr/tests/test_split.py
+++ b/blaze/expr/tests/test_split.py
@@ -46,7 +46,7 @@ def test_mean():
                                           keepdims=True))
 
     assert isrecord(agg.dshape.measure)
-    assert agg_expr.isidentical(agg.total.sum() / agg.count.sum())
+    assert agg_expr.isidentical(agg.total.sum() / agg['count'].sum())
 
 
 def test_var():