Define and use a safe `isinstance` which handles exceptions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0de277c2c..34ef0c3b8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 
 - Fix width measurement of 353 emoji variation sequences https://github.com/Textualize/rich/pull/1832
+- Workaround for edge case of object from Faiss with no `__class__` https://github.com/Textualize/rich/issues/1838
 
 ## [11.0.0] - 2022-01-09
 
diff --git a/rich/_inspect.py b/rich/_inspect.py
index 262695b1c..07b1b623b 100644
--- a/rich/_inspect.py
+++ b/rich/_inspect.py
@@ -3,7 +3,7 @@
 from inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature
 from typing import Any, Iterable, Optional, Tuple
 
-from .console import RenderableType, Group
+from .console import Group, RenderableType
 from .highlighter import ReprHighlighter
 from .jupyter import JupyterMixin
 from .panel import Panel
@@ -204,7 +204,8 @@ def safe_getattr(attr_name: str) -> Tuple[Any, Any]:
                 add_row(key_text, Pretty(value, highlighter=highlighter))
         if items_table.row_count:
             yield items_table
-        else:
+        elif not_shown_count:
             yield Text.from_markup(
-                f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options."
+                f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] "
+                f"Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options."
             )
diff --git a/rich/pretty.py b/rich/pretty.py
index 57e743df4..f42434ef5 100644
--- a/rich/pretty.py
+++ b/rich/pretty.py
@@ -2,7 +2,6 @@
 import dataclasses
 import inspect
 import os
-import re
 import sys
 from array import array
 from collections import Counter, UserDict, UserList, defaultdict, deque
@@ -93,7 +92,7 @@ def _ipy_display_hook(
     from .console import ConsoleRenderable  # needed here to prevent circular import
 
     # always skip rich generated jupyter renderables or None values
-    if isinstance(value, JupyterRenderable) or value is None:
+    if _safe_isinstance(value, JupyterRenderable) or value is None:
         return
 
     console = console or get_console()
@@ -124,12 +123,12 @@ def _ipy_display_hook(
                     return  # Delegate rendering to IPython
 
     # certain renderables should start on a new line
-    if isinstance(value, ConsoleRenderable):
+    if _safe_isinstance(value, ConsoleRenderable):
         console.line()
 
     console.print(
         value
-        if isinstance(value, RichRenderable)
+        if _safe_isinstance(value, RichRenderable)
         else Pretty(
             value,
             overflow=overflow,
@@ -144,6 +143,16 @@ def _ipy_display_hook(
     )
 
 
+def _safe_isinstance(
+    obj: object, class_or_tuple: Union[type, Tuple[type, ...]]
+) -> bool:
+    """isinstance can fail in rare cases, for example types with no __class__"""
+    try:
+        return isinstance(obj, class_or_tuple)
+    except Exception:
+        return False
+
+
 def install(
     console: Optional["Console"] = None,
     overflow: "OverflowMethod" = "ignore",
@@ -178,7 +187,7 @@ def display_hook(value: Any) -> None:
             builtins._ = None  # type: ignore
             console.print(
                 value
-                if isinstance(value, RichRenderable)
+                if _safe_isinstance(value, RichRenderable)
                 else Pretty(
                     value,
                     overflow=overflow,
@@ -355,7 +364,7 @@ def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]:
 def is_expandable(obj: Any) -> bool:
     """Check if an object may be expanded by pretty print."""
     return (
-        isinstance(obj, _CONTAINERS)
+        _safe_isinstance(obj, _CONTAINERS)
         or (is_dataclass(obj))
         or (hasattr(obj, "__rich_repr__"))
         or _is_attr_object(obj)
@@ -539,7 +548,7 @@ def to_repr(obj: Any) -> str:
         """Get repr string for an object, but catch errors."""
         if (
             max_string is not None
-            and isinstance(obj, (bytes, str))
+            and _safe_isinstance(obj, (bytes, str))
             and len(obj) > max_string
         ):
             truncated = len(obj) - max_string
@@ -565,7 +574,7 @@ def _traverse(obj: Any, root: bool = False, depth: int = 0) -> Node:
 
         def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:
             for arg in rich_args:
-                if isinstance(arg, tuple):
+                if _safe_isinstance(arg, tuple):
                     if len(arg) == 3:
                         key, child, default = arg
                         if default == child:
@@ -622,7 +631,7 @@ def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:
                             last=root,
                         )
                     for last, arg in loop_last(args):
-                        if isinstance(arg, tuple):
+                        if _safe_isinstance(arg, tuple):
                             key, child = arg
                             child_node = _traverse(child, depth=depth + 1)
                             child_node.last = last
@@ -689,7 +698,7 @@ def iter_attrs() -> Iterable[
 
         elif (
             is_dataclass(obj)
-            and not isinstance(obj, type)
+            and not _safe_isinstance(obj, type)
             and not fake_attributes
             and (_is_dataclass_repr(obj) or py_version == (3, 6))
         ):
@@ -722,9 +731,9 @@ def iter_attrs() -> Iterable[
 
                 pop_visited(obj_id)
 
-        elif isinstance(obj, _CONTAINERS):
+        elif _safe_isinstance(obj, _CONTAINERS):
             for container_type in _CONTAINERS:
-                if isinstance(obj, container_type):
+                if _safe_isinstance(obj, container_type):
                     obj_type = container_type
                     break
 
@@ -752,7 +761,7 @@ def iter_attrs() -> Iterable[
                 num_items = len(obj)
                 last_item_index = num_items - 1
 
-                if isinstance(obj, _MAPPING_CONTAINERS):
+                if _safe_isinstance(obj, _MAPPING_CONTAINERS):
                     iter_items = iter(obj.items())
                     if max_length is not None:
                         iter_items = islice(iter_items, max_length)
@@ -777,7 +786,7 @@ def iter_attrs() -> Iterable[
             pop_visited(obj_id)
         else:
             node = Node(value_repr=to_repr(obj), last=root)
-        node.is_tuple = isinstance(obj, tuple)
+        node.is_tuple = _safe_isinstance(obj, tuple)
         return node
 
     node = _traverse(_object, root=True)
@@ -812,13 +821,13 @@ def pretty_repr(
         str: A possibly multi-line representation of the object.
     """
 
-    if isinstance(_object, Node):
+    if _safe_isinstance(_object, Node):
         node = _object
     else:
         node = traverse(
             _object, max_length=max_length, max_string=max_string, max_depth=max_depth
         )
-    repr_str = node.render(
+    repr_str: str = node.render(
         max_width=max_width, indent_size=indent_size, expand_all=expand_all
     )
     return repr_str
diff --git a/tests/test_inspect.py b/tests/test_inspect.py
index 5a9a57610..05a78b871 100644
--- a/tests/test_inspect.py
+++ b/tests/test_inspect.py
@@ -6,7 +6,6 @@
 from rich import inspect
 from rich.console import Console
 
-
 skip_py36 = pytest.mark.skipif(
     sys.version_info.minor == 6 and sys.version_info.major == 3,
     reason="rendered differently on py3.6",
@@ -260,3 +259,18 @@ class Foo:
     result = render(foo, methods=True, width=100)
     print(repr(result))
     assert expected == result
+
+
+def test_inspect_swig_edge_case():
+    """Issue #1838 - Edge case with Faiss library - object with empty dir()"""
+
+    class Thing:
+        @property
+        def __class__(self):
+            raise AttributeError
+
+    thing = Thing()
+    try:
+        inspect(thing)
+    except Exception as e:
+        assert False, f"Object with no __class__ shouldn't raise {e}"

## Type of changes

- [x] Bug fix
- [ ] New feature
- [ ] Documentation / docstrings
- [ ] Tests
- [ ] Other

## Checklist

- [x] I've run the latest [black](https://github.com/psf/black) with default args on new code.
- [x] I've updated CHANGELOG.md and CONTRIBUTORS.md where appropriate.
- [x] I've added tests for new code.
- [x] I accept that @willmcgugan may be pedantic in the code review.

## Description

The "Swig global variables object" has an empty `dir()` (🤷‍♂️) so that attribute access of `.__class__` fails. I'm not sure what else we can do other than patch this particular case by checking for the `__class__` attribute.

Fixes #1838 
