Repository: ipython/ipython

Issue:
OrderedDict output differs in ipython from python (and official documentation)
[collections — Container datatypes](https://docs.python.org/3/library/collections.html#collections.OrderedDict)

An ordered dictionary can be combined with the Counter class so that the counter remembers the order elements are first encountered:

```
from collections import Counter, OrderedDict

class OrderedCounter(Counter, OrderedDict):
    'Counter that remembers the order elements are first encountered'

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))

    def __reduce__(self):
        return self.__class__, (OrderedDict(self),)
```
This is based off a Raymond Hettinger - Super considered super! - PyCon 2015, and his blog post [Python’s super() considered super!](https://rhettinger.wordpress.com/2011/05/26/super-considered-super/).

My confusion is IPython returns a result that differs from the Python it's built on.  The ordering of the output is important and the recipe in Hettinger's blog and the official documentation preserves the insertion order but IPython 6.1.0 built on Python 3.6.3 does not.  However if I execute directly in Python 3.6.3 I receive the expected desired result.

`oc = OrderedCounter('abracadabra')`

output in Python 3.6.3 (and Python 2.7.14)
`OrderedCounter(OrderedDict([('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)]))`

output in IPython 6.1.0 on Python 3.6.3 Anaconda
`OrderedCounter({'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r': 2})`  //ordering is not preserved, seems sorted by key

Would someone kindly explain why the output is different in IPython 6.1.0


Discussion:
I know the above recipe isn't really needed in python 3.6 as dictionary preserves insert order (not absolute guaranteed until maybe 3.7).
```
from collections import Counter
c = Counter('abracadabra')
```
output 3.6.3
`Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})`

Here, too, IPython seems to be doing some implicit sort.
output IPython 6.1
`Counter({'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r': 2})`

Resolving patch:
diff --git a/IPython/lib/pretty.py b/IPython/lib/pretty.py
--- a/IPython/lib/pretty.py
+++ b/IPython/lib/pretty.py
@@ -392,6 +392,10 @@ def pretty(self, obj):
                             meth = cls._repr_pretty_
                             if callable(meth):
                                 return meth(obj, self, cycle)
+                        if cls is not object \
+                                and callable(cls.__dict__.get('__repr__')):
+                            return _repr_pprint(obj, self, cycle)
+
             return _default_pprint(obj, self, cycle)
         finally:
             self.end_group()
@@ -537,17 +541,12 @@ def _default_pprint(obj, p, cycle):
     p.end_group(1, '>')
 
 
-def _seq_pprinter_factory(start, end, basetype):
+def _seq_pprinter_factory(start, end):
     """
     Factory that returns a pprint function useful for sequences.  Used by
     the default pprint for tuples, dicts, and lists.
     """
     def inner(obj, p, cycle):
-        typ = type(obj)
-        if basetype is not None and typ is not basetype and typ.__repr__ != basetype.__repr__:
-            # If the subclass provides its own repr, use it instead.
-            return p.text(typ.__repr__(obj))
-
         if cycle:
             return p.text(start + '...' + end)
         step = len(start)
@@ -564,21 +563,16 @@ def inner(obj, p, cycle):
     return inner
 
 
-def _set_pprinter_factory(start, end, basetype):
+def _set_pprinter_factory(start, end):
     """
     Factory that returns a pprint function useful for sets and frozensets.
     """
     def inner(obj, p, cycle):
-        typ = type(obj)
-        if basetype is not None and typ is not basetype and typ.__repr__ != basetype.__repr__:
-            # If the subclass provides its own repr, use it instead.
-            return p.text(typ.__repr__(obj))
-
         if cycle:
             return p.text(start + '...' + end)
         if len(obj) == 0:
             # Special case.
-            p.text(basetype.__name__ + '()')
+            p.text(type(obj).__name__ + '()')
         else:
             step = len(start)
             p.begin_group(step, start)
@@ -596,17 +590,12 @@ def inner(obj, p, cycle):
     return inner
 
 
-def _dict_pprinter_factory(start, end, basetype=None):
+def _dict_pprinter_factory(start, end):
     """
     Factory that returns a pprint function used by the default pprint of
     dicts and dict proxies.
     """
     def inner(obj, p, cycle):
-        typ = type(obj)
-        if basetype is not None and typ is not basetype and typ.__repr__ != basetype.__repr__:
-            # If the subclass provides its own repr, use it instead.
-            return p.text(typ.__repr__(obj))
-
         if cycle:
             return p.text('{...}')
         step = len(start)
@@ -745,12 +734,12 @@ def _exception_pprint(obj, p, cycle):
     int:                        _repr_pprint,
     float:                      _repr_pprint,
     str:                        _repr_pprint,
-    tuple:                      _seq_pprinter_factory('(', ')', tuple),
-    list:                       _seq_pprinter_factory('[', ']', list),
-    dict:                       _dict_pprinter_factory('{', '}', dict),
+    tuple:                      _seq_pprinter_factory('(', ')'),
+    list:                       _seq_pprinter_factory('[', ']'),
+    dict:                       _dict_pprinter_factory('{', '}'),
     
-    set:                        _set_pprinter_factory('{', '}', set),
-    frozenset:                  _set_pprinter_factory('frozenset({', '})', frozenset),
+    set:                        _set_pprinter_factory('{', '}'),
+    frozenset:                  _set_pprinter_factory('frozenset({', '})'),
     super:                      _super_pprint,
     _re_pattern_type:           _re_pattern_pprint,
     type:                       _type_pprint,


Test patch:
diff --git a/IPython/core/tests/test_formatters.py b/IPython/core/tests/test_formatters.py
--- a/IPython/core/tests/test_formatters.py
+++ b/IPython/core/tests/test_formatters.py
@@ -49,7 +49,7 @@ def test_pretty():
     f = PlainTextFormatter()
     f.for_type(A, foo_printer)
     nt.assert_equal(f(A()), 'foo')
-    nt.assert_equal(f(B()), 'foo')
+    nt.assert_equal(f(B()), 'B()')
     nt.assert_equal(f(GoodPretty()), 'foo')
     # Just don't raise an exception for the following:
     f(BadPretty())
diff --git a/IPython/lib/tests/test_pretty.py b/IPython/lib/tests/test_pretty.py
--- a/IPython/lib/tests/test_pretty.py
+++ b/IPython/lib/tests/test_pretty.py
@@ -420,4 +420,24 @@ def meaning_of_life(question=None):
         return "Don't panic"
 
     nt.assert_in('meaning_of_life(question=None)', pretty.pretty(meaning_of_life))
-    
+
+
+class OrderedCounter(Counter, OrderedDict):
+    'Counter that remembers the order elements are first encountered'
+
+    def __repr__(self):
+        return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))
+
+    def __reduce__(self):
+        return self.__class__, (OrderedDict(self),)
+
+class MySet(set):  # Override repr of a basic type
+    def __repr__(self):
+        return 'mine'
+
+def test_custom_repr():
+    """A custom repr should override a pretty printer for a parent type"""
+    oc = OrderedCounter("abracadabra")
+    nt.assert_in("OrderedCounter(OrderedDict", pretty.pretty(oc))
+
+    nt.assert_equal(pretty.pretty(MySet()), 'mine')