Spaces have a compatible method which determines if 2 Spaces are of t…
diff --git a/gym/core.py b/gym/core.py
index f0204e9dedb..5bbb1250f48 100644
--- a/gym/core.py
+++ b/gym/core.py
@@ -184,6 +184,24 @@ def from_jsonable(self, sample_n):
         # By default, assume identity is JSONable
         return sample_n
 
+    def compatible(self, space):
+        """
+        Return boolean specifying if space is compatible with this Space
+        (equal shape structure, ignoring bounds).  None matches any Space.
+        """
+        # allow None to match with any space
+        if space is None:
+            return True
+
+        # compare classes
+        if type(self) != type(space):
+            return False
+
+        # TODO - compare dtypes?
+
+        # compare shapes
+        return self.shape == space.shape
+
 
 warn_once = True
 
diff --git a/gym/spaces/dict_space.py b/gym/spaces/dict_space.py
index 1c0d90af183..2ee1f929489 100644
--- a/gym/spaces/dict_space.py
+++ b/gym/spaces/dict_space.py
@@ -70,3 +70,19 @@ def from_jsonable(self, sample_n):
                 entry[key] = value[i]
             ret.append(entry)
         return ret
+
+    def compatible(self, space):
+        if not super(Dict, self).compatible(space):
+            return False
+
+        # compare each subspace
+        for k, subspace_x in self.spaces.items():
+            subspace_y = space.spaces[k]
+
+            # allow None to match any Space
+            if subspace_x is None or subspace_y is None:
+                continue
+
+            if not subspace_x.compatible(subspace_y):
+                return False
+        return True
diff --git a/gym/spaces/tests/test_compatible_spaces.py b/gym/spaces/tests/test_compatible_spaces.py
new file mode 100644
index 00000000000..2aa32f0f4c8
--- /dev/null
+++ b/gym/spaces/tests/test_compatible_spaces.py
@@ -0,0 +1,56 @@
+import pytest
+import numpy as np
+import copy
+import random
+from gym.spaces import Box, Dict, Tuple
+
+
+def test_compatibility():
+    # create all testable spaces
+    spaces = [
+        Box(-1.0, 1.0, (20, ), np.float32),
+        Box(-1.0, 1.0, (40, ), np.float32),
+        Box(-1.0, 1.0, (20, 20), np.float32),
+        Box(-1.0, 1.0, (20, 24), np.float32),
+        Dict({'A': Box(-1.0, 1.0, (20, ), np.float32), 'B': Box(-1.0, 1.0, (20, ), np.float32)}),
+        Dict({'A': Box(-1.0, 1.0, (20, ), np.float32), 'B': Box(-1.0, 1.0, (40, ), np.float32)}),
+        Dict({'A': Box(-1.0, 1.0, (40, ), np.float32), 'B': Box(-1.0, 1.0, (20, ), np.float32)}),
+        Tuple([Box(-1.0, 1.0, (20, ), np.float32), Box(-1.0, 1.0, (20, ), np.float32)]),
+        Tuple([Box(-1.0, 1.0, (40, ), np.float32), Box(-1.0, 1.0, (20, ), np.float32)]),
+        Tuple([Box(-1.0, 1.0, (20, ), np.float32), Box(-1.0, 1.0, (40, ), np.float32)]),
+    ]
+
+    # iterate and compare all combinations
+    spaces_range = range(len(spaces))
+    for x in spaces_range:
+        space1 = spaces[x]
+        for y in spaces_range:
+            # create copy of Space with random bounds
+            space2 = randomize_space_bounds(spaces[y])
+
+            expected_compatible = x == y
+            actual_compatible = space1.compatible(space2)
+            assert expected_compatible == actual_compatible
+
+def randomize_space_bounds(space):
+    # copy space
+    space = copy.copy(space)
+
+    # check if space contain subspaces
+    if hasattr(space, "spaces"):
+        # compare each sub-Space
+        subspaces = space.spaces
+        if hasattr(subspaces, "keys"):
+            iterable = subspaces.keys()
+        else:
+            iterable = range(len(subspaces))
+        for k in iterable:
+            space.spaces[k] = randomize_space_bounds(space.spaces[k])
+
+    # randomize bounds
+    if hasattr(space, "low"):
+        space.low = random.uniform(-5.0, 5.0)
+    if hasattr(space, "high"):
+        space.high = random.uniform(-5.0, 5.0)
+
+    return space
diff --git a/gym/spaces/tuple_space.py b/gym/spaces/tuple_space.py
index 453663781f4..74793a58571 100644
--- a/gym/spaces/tuple_space.py
+++ b/gym/spaces/tuple_space.py
@@ -30,3 +30,20 @@ def to_jsonable(self, sample_n):
 
     def from_jsonable(self, sample_n):
         return [sample for sample in zip(*[space.from_jsonable(sample_n[i]) for i, space in enumerate(self.spaces)])]
+
+    def compatible(self, space):
+        if not super(Tuple, self).compatible(space):
+            return False
+
+        # compare each subspace
+        for i in range(len(self.spaces)):
+            subspace_x = self.spaces[i]
+            subspace_y = space.spaces[i]
+
+            # allow None to match any Space
+            if subspace_x is None or subspace_y is None:
+                continue
+
+            if not subspace_x.compatible(subspace_y):
+                return False
+        return True

…he same shape, ignoring bounds.