Prevent adding new attributes to the accessors .str, .dt and .cat
State: closed

assigning to `Series.str`, `Series.dt`, or `Series.cat` was not failing
although you couldn't get the value back:

```
In[10]: a = pandas.Series(pandas.Categorical(list("abc")))
In[11]: a.cat.labels = [1,2]
In[12]: a.cat.labels
Traceback (most recent call last):
  File "C:\portabel\miniconda\envs\pandas_dev\lib\site-packages\IPython\core\interactiveshell.py", line 2883, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-12-a68ee763e4e8>", line 1, in <module>
    a.cat.labels
AttributeError: 'CategoricalAccessor' object has no attribute 'labels'
```

Now we fail early:

```
In[10]: a = pandas.Series(pandas.Categorical(list("abc")))
In[11]: a.cat.labels = [1,2]
Traceback (most recent call last):
  File "C:\data\external\pandas\pandas\tests\test_categorical.py", line 3633, in test_cat_accessor_no_new_attributes
    c.cat.labels = [1,2]
  File "C:\data\external\pandas\pandas\core\base.py", line 121, in __setattr__
    raise AttributeError( "You cannot add any new attribute '{key}'".format(key=key))
AttributeError: You cannot add any new attribute 'labels'
```

Closes: https://github.com/pydata/pandas/issues/10673


Labels:
name: API Design
name: Error Reporting
description: Incorrect or improved errors from pandas

Comments:
I've had some pickling issues with doing this in the past, as `__setattr__` gets called during unpickling, without `__init__` being called.
Another way - slightly less elegant but lower blast radius - is to set a private variable `_str` and then have a property `def str(self): ; return self._str`. That'll also prevent setting of `str`.

I agree here with @maximilianr 
this is already done in `NDFrame` using `_accessors` as a white-list with `__getattr__`, so should be straightforward, so I think would be ok to emulate in `PandasDelegate` (not that these allow sub-classes to override, e.g. `Series` does for `.dt,.str.,.cat` themselves.

I'm not sure I can follow: as far as I can see, the accessors `.str`, `.dt` and `.cat` are not pickled (and they should not!). This is not trying to prevent `s.str = something` but `s.str.someattr = something`. The main benefit is that a user, who misspells an attribute, is not surprised. E.g. when using `s.cat.labels` instead of `s.cat.categories`. 

Currently assigning (`s.cat.labels = "aaa"`) works, but you can't get the value from this attribute back (`print(s.cat.labels)` raises an `AttributeError`). I've actually no idea where this behaviour comes from, the last should work, as the first (without this PR) uses the normal item assignment and the latter normal get (that's at least what the pycharm debugger lets me see).

This needs a rebase if pulled after #11582...

Ok, took me a while to find a place for the mixin: both core.common and core.base resulted in circular depencies due to the way the StringMethods are defined (`str = ...(StringMethods,...)` -> can't get the import to a function :-():

```
Traceback (most recent call last):
  File "C:\Program Files (x86)\JetBrains\PyCharm Community Edition 143.165\helpers\pycharm\utrunner.py", line 121, in <module>
    modules = [loadSource(a[0])]
  File "C:\Program Files (x86)\JetBrains\PyCharm Community Edition 143.165\helpers\pycharm\utrunner.py", line 41, in loadSource
    module = imp.load_source(moduleName, fileName)
  File "C:\data\external\pandas\pandas\tests\test_base.py", line 6, in <module>
    import pandas.compat as compat
  File "C:\data\external\pandas\pandas\__init__.py", line 42, in <module>
    import pandas.core.config_init
  File "C:\data\external\pandas\pandas\core\config_init.py", line 17, in <module>
    from pandas.core.format import detect_console_encoding
  File "C:\data\external\pandas\pandas\core\format.py", line 8, in <module>
    from pandas.core.base import PandasObject
  File "C:\data\external\pandas\pandas\core\base.py", line 10, in <module>
    from pandas.core.strings import StringMethods
  File "C:\data\external\pandas\pandas\core\strings.py", line 4, in <module>
    from pandas.core.base import NoNewAttributesMixin
ImportError: cannot import name NoNewAttributesMixin
```

BTW: The Series and Indexes are kind of inconsistent:
- Series only has accessors, so no datetime/string/categorical related functions directly exposed under `Series` but `.dt`, `.cat`, which are implemented as `Delegate` and `.str`, which is no `Delegate`
-  `CategoricalIndex` is a `Delegate` directly (-> `CatIndex.codes` works directly without `CatIndex.cat.codes`)
- `DatetimeIndex` is not a Delegate, but exposes it's methods directly, too, via mixins
- There is no `StringIndex` but `Index.str....` exists (via `IndexOpsMixin`) for all Indexes?

@JanSchulz 

those are not inconsistent, merely implementations. `DatetimeIndex`,  actually defines things (IOW it is the 'target' of the delegation). `CategoricalIndex` _uses_ the `Delegate` (so its like `Series` in this respect). `Index`/`Series` are also users of `.str`.

I disagree: Consistent would be either:
- Series and Index would use accessors, which are delegates to the real data below (like `Series.cat` or to a wrapper like in `Series.str` / `Series.dt` (ok, preoblematic, because we currently don't have real methods on a string/datetime objects, only numpy/pandas functions. Or the `DateTimeIndex`, which is used like one...)
- Series and Index would both have subclasses like `DateTimeSeries` (does not exist) or `DateTimeIndex` (does exist), which expose the API directly.

So, move the string part to a new mixin in `string.py`.. If this is green, I think this should be ready to merge...

@JanSchulz looks good! ping on green.

Seems travis is dead?!

Ok, I will rebase #11582 on top of this PR, to shortcut that process a bit...

thanks @JanSchulz 

go ahead and rebase #11582