Repository: ipython/ipython

Issue:
Autoreload Enum Error
I'm getting a weird error when trying to use autoreload and Enums. I have an Enum class defined in a file as:
```python
class MyEnum(Enum):
    A = 'A'
    B = 'B'
```
Using `%autoreload 2`, if I try to add `C = 'C'` to `MyEnum`, or remove an existing definition, I get the following error:
```
[autoreload of updates_type failed: Traceback (most recent call last):
  File "/home/amarder/anaconda3/lib/python3.5/site-packages/IPython/extensions/autoreload.py", line 247, in check
    superreload(m, reload, self.old_objects)
RecursionError: maximum recursion depth exceeded
]
```
Sometimes it is a different error:
```
[autoreload of updates_type failed: Traceback (most recent call last):
  File "/home/amarder/anaconda3/lib/python3.5/site-packages/IPython/extensions/autoreload.py", line 247, in check
    superreload(m, reload, self.old_objects)
AttributeError: __abstractmethods__
]
```

I'm running the code on Debian Linux, using Anaconda with python 3.5.


Discussion:
autoreload is a bit black magic, so it's not a big surprise when it breaks.

I'd guess that the recursion error is because enum values have a reference back to their class (e.g. `MyEnum.A.__objclass__`, so there's an infinite chain of attributes that it's trying to replace: `MyEnum.A.__objclass__.A.__objclass__.<etc>`
If that's true, is there a way to mark a class as having been already reloaded so autoreload does not try again?
Not at present. Actually, I now think my guess was wrong; MyEnum.A is not a type, so it should be updated like that. You can see the code updating a class object here:
https://github.com/ipython/ipython/blob/e8341164d5f525a3bb3bf440cb042cc8145a5642/IPython/extensions/autoreload.py#L276
seem like in `update_class` the two references `old_obj` and  `new_obj` can be the same (I'm going to guess that's the point of enums, where instances are shared. 
So `continue` if they are the same should be enough. 
also we should bump the number of stacks shown to something like 10-ish (or make it configurable) to be able to debug things like that better. 

Resolving patch:
diff --git a/IPython/extensions/autoreload.py b/IPython/extensions/autoreload.py
--- a/IPython/extensions/autoreload.py
+++ b/IPython/extensions/autoreload.py
@@ -278,9 +278,10 @@ def update_class(old, new):
     method code objects"""
     for key in list(old.__dict__.keys()):
         old_obj = getattr(old, key)
-
         try:
             new_obj = getattr(new, key)
+            if old_obj == new_obj:
+                continue
         except AttributeError:
             # obsolete attribute: remove it
             try:


Test patch:
diff --git a/IPython/extensions/tests/test_autoreload.py b/IPython/extensions/tests/test_autoreload.py
--- a/IPython/extensions/tests/test_autoreload.py
+++ b/IPython/extensions/tests/test_autoreload.py
@@ -15,6 +15,7 @@
 import os
 import sys
 import tempfile
+import textwrap
 import shutil
 import random
 import time
@@ -23,6 +24,8 @@
 import nose.tools as nt
 import IPython.testing.tools as tt
 
+from IPython.testing.decorators import skipif
+
 from IPython.extensions.autoreload import AutoreloadMagics
 from IPython.core.events import EventManager, pre_run_cell
 
@@ -126,6 +129,29 @@ def new_module(self, code):
 #-----------------------------------------------------------------------------
 
 class TestAutoreload(Fixture):
+
+    @skipif(sys.version_info < (3, 6))
+    def test_reload_enums(self):
+        import enum
+        mod_name, mod_fn = self.new_module(textwrap.dedent("""
+                                from enum import Enum
+                                class MyEnum(Enum):
+                                    A = 'A'
+                                    B = 'B'
+                            """))
+        self.shell.magic_autoreload("2")
+        self.shell.magic_aimport(mod_name)
+        self.write_file(mod_fn, textwrap.dedent("""
+                                from enum import Enum
+                                class MyEnum(Enum):
+                                    A = 'A'
+                                    B = 'B'
+                                    C = 'C'
+                            """))
+        with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
+            self.shell.run_code("pass")  # trigger another reload
+
+
     def _check_smoketest(self, use_aimport=True):
         """
         Functional test for the automatic reloader using either