Repository: sympy/sympy

Issue:
Any dict-like object in expr.subs
https://github.com/sympy/sympy/blob/master/sympy/core/basic.py#L836
Explicitly checks for instances of `Dict` or `dict`.
Realistically this should be anything `dict`-like, for example a `collections.ChainMap` object.

As `Dict` is your own wrapper, it is up to you how to handle it's inheritance, however I suggest rather than, or in addition to `dict`, you have `collections.abc.Mapping`. All standard library `dict`-like objects are instances of `collections.abc.Mapping`, thus this should cover all cases within the stdlib.

Currently I have to wrap my `ChainMap`s with a `dict()` call, which wastes computational time and memory; when duct-typing should be satisfied with `OrderedDict` or `ChainMap` as a drop-in replacements for `dict`.

Discussion:

Patch:
diff --git a/sympy/core/basic.py b/sympy/core/basic.py
index d8dec8588a..bc9fef1d79 100644
--- a/sympy/core/basic.py
+++ b/sympy/core/basic.py
@@ -1,5 +1,6 @@
 """Base class for all the objects in SymPy"""
 from __future__ import print_function, division
+from collections import Mapping
 
 from .assumptions import BasicMeta, ManagedProperties
 from .cache import cacheit
@@ -833,7 +834,7 @@ def subs(self, *args, **kwargs):
             sequence = args[0]
             if isinstance(sequence, set):
                 unordered = True
-            elif isinstance(sequence, (Dict, dict)):
+            elif isinstance(sequence, (Dict, Mapping)):
                 unordered = True
                 sequence = sequence.items()
             elif not iterable(sequence):
diff --git a/sympy/functions/combinatorial/numbers.py b/sympy/functions/combinatorial/numbers.py
index 75a2ae6bca..f4d2bc28ce 100644
--- a/sympy/functions/combinatorial/numbers.py
+++ b/sympy/functions/combinatorial/numbers.py
@@ -164,7 +164,6 @@ def _eval_rewrite_as_sqrt(self, n):
 #                                                                            #
 #----------------------------------------------------------------------------#
 
-
 class bernoulli(Function):
     r"""
     Bernoulli numbers / Bernoulli polynomials
@@ -1003,6 +1002,7 @@ def _eval_is_prime(self):
         return (n - 8).is_zero
 
 
+
 #######################################################################
 ###
 ### Functions for enumerating partitions, permutations and combinations


Test patch:
diff --git a/sympy/core/tests/test_basic.py b/sympy/core/tests/test_basic.py
index 148082694c..c2de60073f 100644
--- a/sympy/core/tests/test_basic.py
+++ b/sympy/core/tests/test_basic.py
@@ -1,6 +1,9 @@
 """This tests sympy/core/basic.py with (ideally) no reference to subclasses
 of Basic or Atom."""
 
+import collections
+import sys
+
 from sympy.core.basic import Basic, Atom, preorder_traversal
 from sympy.core.singleton import S, Singleton
 from sympy.core.symbol import symbols
@@ -64,6 +67,10 @@ def test_subs():
     assert b21.subs([(b2, b1), (b1, b2)]) == Basic(b2, b2)
 
     assert b21.subs({b1: b2, b2: b1}) == Basic(b2, b2)
+    if sys.version_info >= (3, 3):
+        assert b21.subs(collections.ChainMap({b1: b2}, {b2: b1})) == Basic(b2, b2)
+    if sys.version_info >= (2, 7):
+        assert b21.subs(collections.OrderedDict([(b2, b1), (b1, b2)])) == Basic(b2, b2)
 
     raises(ValueError, lambda: b21.subs('bad arg'))
     raises(ValueError, lambda: b21.subs(b1, b2, b3))