Repository: pandas-dev/pandas

Issue:
pandas.options.__getattr__ raises KeyError instead of AttributeError
#### Code Sample, a copy-pastable example if possible

```python
import pandas as pd
hasattr(pd.options,'bananas') # should return False but instead raises KeyError: 'bananas'
getattr(pd.options,'bananas') # should raise AttributeError but ...
pd.options.__getattr__('bananas') # should raise AttributeError but ...
```

#### Problem description

The [docs](https://docs.python.org/3/reference/datamodel.html#object.__getattr__) are clear that custom \_\_getattr__ methods should return a value or raise AttributeError. pandas.options seems to raise KeyError instead. This can be a problem for generic code that iterates through the attributes of any object and expects hasattr() to behave sanely, e.g. https://github.com/Dyalog/pynapl/blob/c6f34d0323aa7370582204c73757f9cea1072794/pynapl/ObjectWrapper.py#L86

#### Expected Output

```
False
AttributeError: 'bananas'
AttributeError: 'bananas'
```

#### Output of ``pd.show_versions()``

<details>

INSTALLED VERSIONS
------------------
commit: None
python: 3.6.3.final.0
python-bits: 64
OS: Linux
OS-release: 4.13.0-35-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_GB.UTF-8
LOCALE: en_GB.UTF-8

pandas: 0.22.0
pytest: None
pip: 9.0.1
setuptools: 36.2.7
Cython: None
numpy: 1.14.0
scipy: 0.18.1
pyarrow: None
xarray: None
IPython: 5.1.0
sphinx: None
patsy: None
dateutil: 2.6.1
pytz: 2018.3
blosc: None
bottleneck: None
tables: 3.3.0
numexpr: 2.6.2
feather: None
matplotlib: 2.0.0
openpyxl: None
xlrd: 1.0.0
xlwt: None
xlsxwriter: 0.9.6
lxml: None
bs4: 4.6.0
html5lib: 0.999999999
sqlalchemy: None
pymysql: 0.7.11.None
psycopg2: None
jinja2: 2.9.6
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None

</details>

pandas.options.__getattr__ raises KeyError instead of AttributeError
#### Code Sample, a copy-pastable example if possible

```python
import pandas as pd
hasattr(pd.options,'bananas') # should return False but instead raises KeyError: 'bananas'
getattr(pd.options,'bananas') # should raise AttributeError but ...
pd.options.__getattr__('bananas') # should raise AttributeError but ...
```

#### Problem description

The [docs](https://docs.python.org/3/reference/datamodel.html#object.__getattr__) are clear that custom \_\_getattr__ methods should return a value or raise AttributeError. pandas.options seems to raise KeyError instead. This can be a problem for generic code that iterates through the attributes of any object and expects hasattr() to behave sanely, e.g. https://github.com/Dyalog/pynapl/blob/c6f34d0323aa7370582204c73757f9cea1072794/pynapl/ObjectWrapper.py#L86

#### Expected Output

```
False
AttributeError: 'bananas'
AttributeError: 'bananas'
```

#### Output of ``pd.show_versions()``

<details>

INSTALLED VERSIONS
------------------
commit: None
python: 3.6.3.final.0
python-bits: 64
OS: Linux
OS-release: 4.13.0-35-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_GB.UTF-8
LOCALE: en_GB.UTF-8

pandas: 0.22.0
pytest: None
pip: 9.0.1
setuptools: 36.2.7
Cython: None
numpy: 1.14.0
scipy: 0.18.1
pyarrow: None
xarray: None
IPython: 5.1.0
sphinx: None
patsy: None
dateutil: 2.6.1
pytz: 2018.3
blosc: None
bottleneck: None
tables: 3.3.0
numexpr: 2.6.2
feather: None
matplotlib: 2.0.0
openpyxl: None
xlrd: 1.0.0
xlwt: None
xlsxwriter: 0.9.6
lxml: None
bs4: 4.6.0
html5lib: 0.999999999
sqlalchemy: None
pymysql: 0.7.11.None
psycopg2: None
jinja2: 2.9.6
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None

</details>



Discussion:
Sure, as pointed out in that issue I think we could raise an `OptionError` in that case as well - want to do a PR?

https://github.com/pandas-dev/pandas/commit/63fc8af2ad6bee9c90766bcb648d27a0b2748a44
Sure, as pointed out in that issue I think we could raise an `OptionError` in that case as well - want to do a PR?

https://github.com/pandas-dev/pandas/commit/63fc8af2ad6bee9c90766bcb648d27a0b2748a44

Resolving patch:
diff --git a/pandas/core/config.py b/pandas/core/config.py
--- a/pandas/core/config.py
+++ b/pandas/core/config.py
@@ -196,7 +196,10 @@ def __getattr__(self, key):
         if prefix:
             prefix += "."
         prefix += key
-        v = object.__getattribute__(self, "d")[key]
+        try:
+            v = object.__getattribute__(self, "d")[key]
+        except KeyError:
+            raise OptionError("No such option")
         if isinstance(v, dict):
             return DictWrapper(v, prefix)
         else:


Test patch:
diff --git a/pandas/tests/test_config.py b/pandas/tests/test_config.py
--- a/pandas/tests/test_config.py
+++ b/pandas/tests/test_config.py
@@ -428,3 +428,9 @@ def test_option_context_scope(self):
 
         # Ensure the current context is reset
         assert self.cf.get_option(option_name) == original_value
+
+    def test_dictwrapper_getattr(self):
+        options = self.cf.options
+        # GH 19789
+        pytest.raises(self.cf.OptionError, getattr, options, 'bananas')
+        assert not hasattr(options, 'bananas')