Repository: pandas-dev/pandas

Issue:
HDFStore.select_column
#### Code Sample, a copy-pastable example if possible

Let's select column from a non-existing dataframe in a HDFStore:

```python
import pandas as pd

store = pd.HDFStore('test.hdf5', mode='w')
store.select_column('dummy', 'index')
```
#### Problem description

We get an `AttributeError` because `get_storer` returns `None`:

```
Traceback (most recent call last):
  File "pandas_hdf5.py", line 4, in <module>
    store.select_column('dummy', 'index')
  File "[...]/site-packages/pandas/io/pytables.py", line 778, in select_column
    return self.get_storer(key).read_column(column=column, **kwargs)
AttributeError: 'NoneType' object has no attribute 'read_column'
```

Is this intended?

#### Expected Output

The docstring says:

```python
            """
            Exceptions
            ----------
            raises KeyError if the column is not found (or key is not a valid
                store)
            raises ValueError if the column can not be extracted individually (it
                is part of a data block)
            """
```

Shouldn't I expect a `KeyError`, then?

It could be just this simple patch:

```
    - return self.get_storer(key).read_column(column=column, **kwargs)
    + storer = self.get_storer(key)
    + if storer is None:
    +    raise KeyError('{} not in {}'.format(key, self))
    + return storer.read_column(column=column, **kwargs)
```

or should `get_storer` raise en exception in the first place?

I'm new to Pandas/PyTables so I don't have the big picture.

From a caller perspective, I could to check first that the dataframe is in the store:

```python
store = pd.HDFStore('test.hdf5', mode='w')
if 'dummy' in store:
    store.select_column('dummy', 'index')
```

but I'd rather "ask forgiveness not permission",

```python
store = pd.HDFStore('test.hdf5', mode='w')
try:
    store.select_column('dummy', 'index')
except AttributeError:
    [...]
```

so I should catch `AttributeError` but I'm not sure this exception being thrown is a design choice.

I hope I'm being constructive and I don't sound like I'm nitpicking.

#### Output of ``pd.show_versions()``

<details>

INSTALLED VERSIONS
------------------
commit: None
python: 3.5.3.final.0
python-bits: 64
OS: Linux
OS-release: 4.9.0-4-amd64
machine: x86_64
processor: 
byteorder: little
LC_ALL: None
LANG: fr_FR.UTF-8
LOCALE: fr_FR.UTF-8

pandas: 0.20.3
pytest: 3.2.3
pip: 9.0.1
setuptools: 36.6.0
Cython: None
numpy: 1.13.3
scipy: 0.19.1
xarray: None
IPython: None
sphinx: None
patsy: None
dateutil: 2.6.1
pytz: 2017.2
blosc: None
bottleneck: None
tables: 3.4.2
numexpr: 2.6.4
feather: None
matplotlib: None
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml: None
bs4: None
html5lib: 0.999999999
sqlalchemy: None
pymysql: None
psycopg2: None
jinja2: 2.9.6
s3fs: None
pandas_gbq: None
pandas_datareader: None
</details>


Discussion:
Agree this looks buggy, I would probably push the exception down to `get_storer` and see if any tests break, docstring _says_ it raises.  Want to do a PR?
https://github.com/pandas-dev/pandas/blob/5687f9e8f63c325249caabf0c8b7f0bee0a12f09/pandas/io/pytables.py#L1095

I might try to give it a shot.

The error in get_storer would be a `KeyError`, right? Would this be fine?

```python
    def get_storer(self, key):
        """ return the storer object for a key, raise if not in the file """
        group = self.get_node(key)
        if group is None:
-           return None
+           raise KeyError('No {} node in {}'.format(key, self))
        s = self._create_storer(group)
        s.infer_axes()
        return s
```

Resolving patch:
diff --git a/pandas/io/pytables.py b/pandas/io/pytables.py
--- a/pandas/io/pytables.py
+++ b/pandas/io/pytables.py
@@ -887,7 +887,10 @@ def remove(self, key, where=None, start=None, stop=None):
         where = _ensure_term(where, scope_level=1)
         try:
             s = self.get_storer(key)
-        except:
+        except KeyError:
+            # the key is not a valid store, re-raising KeyError
+            raise
+        except Exception:
 
             if where is not None:
                 raise ValueError(
@@ -899,9 +902,6 @@ def remove(self, key, where=None, start=None, stop=None):
                 s._f_remove(recursive=True)
                 return None
 
-        if s is None:
-            raise KeyError('No object named %s in the file' % key)
-
         # remove the node
         if com._all_none(where, start, stop):
             s.group._f_remove(recursive=True)
@@ -1094,7 +1094,8 @@ def get_storer(self, key):
         """ return the storer object for a key, raise if not in the file """
         group = self.get_node(key)
         if group is None:
-            return None
+            raise KeyError('No object named {} in the file'.format(key))
+
         s = self._create_storer(group)
         s.infer_axes()
         return s


Test patch:
diff --git a/pandas/tests/io/test_pytables.py b/pandas/tests/io/test_pytables.py
--- a/pandas/tests/io/test_pytables.py
+++ b/pandas/tests/io/test_pytables.py
@@ -3836,8 +3836,15 @@ def test_read_column(self):
 
         with ensure_clean_store(self.path) as store:
             _maybe_remove(store, 'df')
-            store.append('df', df)
 
+            # GH 17912
+            # HDFStore.select_column should raise a KeyError
+            # exception if the key is not a valid store
+            with pytest.raises(KeyError,
+                               message='No object named index in the file'):
+                store.select_column('df', 'index')
+
+            store.append('df', df)
             # error
             pytest.raises(KeyError, store.select_column, 'df', 'foo')