BishopPhillips
BishopPhillips
BPC Home BPC Python Topic Home BPC RiskManager BPC SurveyManager BPC RiskWiki Learn HTML 5 and CSS Enquiry

Python Techniques - Type Checking

Type Checking Overview.

Author: Jonathan Bishop
AI Revolution

Python provides several ways to check the type of variables or objects, each with its own features and best-use cases. In this section we provide an overview of each of the type checking approaches available in Python. In the next section we drill into the "collectables.abc" module which checks behaviours (like "Iterable") which is an alternative way to approach type checking.  In the final section we look at the Types module which provides checks for functions and some abstract types not easily distinguished using type() or isinstance(). Here's a rundown of the main approaches: 

1. type() Function

  • What it does: Returns the exact type of an object.

  • Usage:

    x = 10
    print(type(x))  # Output: <class 'int'>
    
  • Features:

    • Precise and directly shows the object's type.

    • Can be used for exact type comparisons:

      if type(x) is int:
          print("x is an integer")
      
  • Limitations:

    • Not suited for checking inheritance or behaviours, as it doesn't account for subclasses or behaviour.

2. isinstance() Function

  • What it does: Checks if an object is an instance of a class (or subclass).

  • Usage:

    x = 10 print(isinstance(x, int)) # Output: True

  • Features:

    • Supports inheritance: returns True if the object is of the given type or a subclass of it.

    • Can check against multiple types at once:

      if isinstance(x, (int, float)):
          print("x is a number")
      
  • Limitations:

    • More general; does not distinguish between direct class instances and subclasses.

3. issubclass() Function

  • What it does: Checks if a class is a subclass of another class.

  • Usage:

    class A: pass
    class B(A): pass
    
    print(issubclass(B, A))  # Output: True
    
  • Features:

    • Works with class relationships, useful for validating type hierarchies.

  • Limitations:

    • Only works for classes, not objects.

4. __class__ Attribute

  • What it does: Directly accesses the class of an object.

  • Usage:

    x = [1, 2, 3]
    print(x.__class__)  # Output: <class 'list'>
    
  • Features:

    • Provides the same result as type() but more programmatically accessible.

  • Limitations:

    • Typically less readable than type() for quick checks.

5. collections.abc for Abstract Base Classes

  • What it does: Checks if an object implements certain behaviors (e.g., if it's iterable, a mapping, etc.).

  • Usage:

    from collections.abc import Iterable
    
    x = [1, 2, 3]
    print(isinstance(x, Iterable))  # Output: True
    
  • Features:

    • Useful for duck typing—checks if an object implements a specific interface rather than its type.

  • Limitations:

    • Doesn't distinguish between types, focuses on behavior.

6. type hints (PEP 484)

  • What it does: Provides type annotations for functions, variables, and classes.

  • Usage:

    def add(a: int, b: int) -> int:
        return a + b
    
  • Features:

    • Improves code readability and maintainability.

    • Tools like mypy and IDEs can verify types statically.

  • Limitations:

    • Not enforced at runtime; purely for documentation and static analysis.

7. callable()

  • What it does: Checks if an object is callable (e.g., a function or class instance with a __call__ method).

  • Usage:

    def func(): pass
    print(callable(func))  # Output: True
    
  • Features:

    • Helps check if an object can be invoked like a function.

  • Limitations:

    • Doesn't identify the exact type of callable (e.g., function vs lambda).

8. Custom Type Checking

  • What it does: Implements checks using hasattr() or custom logic.

  • Usage:

    class MyClass:
        def method(self): pass
    
    x = MyClass()
    if hasattr(x, "method"):
        print("x has a 'method' attribute")
    
  • Features:

    • Highly flexible and allows checking for specific attributes or methods.

  • Limitations:

    • Not a standard type check, more of a structural or feature-based check.

9. types Module

  • What it does: Provides names for common Python types like FunctionType, GeneratorType, etc.

  • Usage:

    from types import FunctionType
    
    def func(): pass
    print(isinstance(func, FunctionType))  # Output: True
    
  • Features:

    • Useful for distinguishing between specific built-in types (e.g., functions vs methods).

  • Limitations:

    • Requires importing types and is more specialized.

Which Approach to Use?

  1. Exact Type Check: Use type() when you need to verify the specific class.

  2. Duck Typing or Behavior: Use isinstance() with collections.abc for more flexible checks.

  3. Custom Needs: Combine hasattr() or callable() for checking behaviors beyond standard types.

  4. Static Typing: Use type hints (PEP 484) to improve readability and leverage tools like mypy for checks.

 

Checking Behaviours - collectables.abc

The collections.abc module in Python provides a set of abstract base classes (ABCs) that define common behaviors for container types and other objects. These ABCs allow you to check whether an object implements specific behaviors or interfaces, such as being iterable, a mapping, or a sequence. Here's a breakdown of the key behaviors you can check using collections.abc:

1. Container Behaviors

  • Container: Checks if an object supports the in operator.

    from collections.abc import Container
    print(isinstance([1, 2, 3], Container))  # Output: True
    

2. Iterable and Iterator Behaviors

  • Iterable: Checks if an object can be iterated over (implements __iter__).

    python

    from collections.abc import Iterable
    print(isinstance([1, 2, 3], Iterable))  # Output: True
    
  • Iterator: Checks if an object is an iterator (implements __iter__ and __next__).

    from collections.abc import Iterator
    print(isinstance(iter([1, 2, 3]), Iterator))  # Output: True
    

3. Sequence Behaviors

  • Sequence: Checks if an object supports sequence operations like indexing and slicing.

    from collections.abc import Sequence
    print(isinstance([1, 2, 3], Sequence))  # Output: True
    

4. Mapping and Mutable Mapping Behaviors

  • Mapping: Checks if an object behaves like a dictionary (supports key-value pairs).

    from collections.abc import Mapping
    print(isinstance({"a": 1}, Mapping))  # Output: True
    
  • MutableMapping: Checks if a mapping object is mutable (supports adding/removing items).

    from collections.abc import MutableMapping
    print(isinstance({"a": 1}, MutableMapping))  # Output: True
    

5. Set Behaviors

  • Set: Checks if an object behaves like a set (supports set operations like union, intersection).

    from collections.abc import Set
    print(isinstance({1, 2, 3}, Set))  # Output: True
    
  • MutableSet: Checks if a set object is mutable (supports adding/removing elements).

    python

    from collections.abc import MutableSet
    print(isinstance({1, 2, 3}, MutableSet))  # Output: True
    

6. Mapping View Behaviors

  • MappingView: Checks if an object is a view of a mapping (e.g., dict.keys() or dict.values()).

    from collections.abc import MappingView
    print(isinstance({"a": 1}.keys(), MappingView))  # Output: True
    

7. Callable Behavior

  • Callable: Checks if an object is callable (e.g., a function or an object with __call__).

    from collections.abc import Callable
    print(isinstance(lambda x: x, Callable))  # Output: True
    

8. Sized Behavior

  • Sized: Checks if an object has a length (implements __len__).

    from collections.abc import Sized
    print(isinstance([1, 2, 3], Sized))  # Output: True
    

9. Hashable Behavior

  • Hashable: Checks if an object is hashable (implements __hash__).

    from collections.abc import Hashable
    print(isinstance(42, Hashable))  # Output: True
    

10. Awaitable and Coroutine Behaviors

  • Awaitable: Checks if an object can be awaited (implements __await__).

    from collections.abc import Awaitable
    async def example(): pass
    print(isinstance(example(), Awaitable))  # Output: True
    
  • Coroutine: Checks if an object is a coroutine (implements __await__ and send).

    from collections.abc import Coroutine
    async def example(): pass
    print(isinstance(example(), Coroutine))  # Output: True
    

11. Async Behaviors

  • AsyncIterable: Checks if an object supports asynchronous iteration (implements __aiter__).

    from collections.abc import AsyncIterable
    class AsyncGen:
        async def __aiter__(self): yield 1
    print(isinstance(AsyncGen(), AsyncIterable))  # Output: True
    
  • AsyncIterator: Checks if an object is an asynchronous iterator (implements __anext__).

    from collections.abc import AsyncIterator
    class AsyncGen:
        async def __aiter__(self): yield 1
    print(isinstance(AsyncGen().__aiter__(), AsyncIterator))  # Output: True
    

12. Buffer Protocol

  • Buffer: Checks if an object supports the buffer protocol (e.g., memoryview).

    from collections.abc import Buffer
    print(isinstance(memoryview(b"data"), Buffer))  # Output: True
    

Why Use collections.abc?

  • Duck Typing: Focuses on behavior rather than exact types.

  • Extensibility: You can create your own classes that inherit from these ABCs to ensure they conform to specific behaviors.

  • Readability: Makes it clear what behaviors your code expects from objects.

 

Checking Abstract and Function types - The Types Module

The types module in Python provides names for many specific object types, especially those related to functions, modules, and other built-in constructs. Here's a comprehensive list of the types you can check using the types module, along with examples:

1. Function Types

  • FunctionType: Represents a standard Python function.

    from types import FunctionType
    
    def my_function():
        pass
    
    print(isinstance(my_function, FunctionType))  # Output: True
    
  • LambdaType: Represents a lambda function (alias for FunctionType).

    from types import LambdaType
    
    my_lambda = lambda x: x + 1
    print(isinstance(my_lambda, LambdaType))  # Output: True
    
  • BuiltinFunctionType: Represents built-in functions like len or print.

    from types import BuiltinFunctionType
    
    print(isinstance(len, BuiltinFunctionType))  # Output: True
    
  • BuiltinMethodType: Represents built-in methods of objects (e.g., dict.get).

    from types import BuiltinMethodType
    
    my_dict = {}
    print(isinstance(my_dict.get, BuiltinMethodType))  # Output: True
    

2. Generator and Coroutine Types

  • GeneratorType: Represents a generator object.

    from types import GeneratorType
    
    def my_generator():
        yield 1
    
    gen = my_generator()
    print(isinstance(gen, GeneratorType))  # Output: True
    
  • CoroutineType: Represents a coroutine object.

    from types import CoroutineType
    
    async def my_coroutine():
        pass
    
    coro = my_coroutine()
    print(isinstance(coro, CoroutineType))  # Output: True
    
  • AsyncGeneratorType: Represents an asynchronous generator object.

    from types import AsyncGeneratorType
    
    async def my_async_generator():
        yield 1
    
    async_gen = my_async_generator()
    print(isinstance(async_gen, AsyncGeneratorType))  # Output: True
    

3. Code and Frame Types

  • CodeType: Represents a code object (compiled Python code).

    from types import CodeType
    
    def my_function():
        pass
    
    print(isinstance(my_function.__code__, CodeType))  # Output: True
    
  • FrameType: Represents a stack frame object.

    import sys
    from types import FrameType
    
    frame = sys._getframe()
    print(isinstance(frame, FrameType))  # Output: True
    

4. Module and Class Types

  • ModuleType: Represents a module object.

    import math
    from types import ModuleType
    
    print(isinstance(math, ModuleType))  # Output: True
    
  • ClassType: Represents old-style classes in Python 2 (not used in Python 3).

5. Method Types

  • MethodType: Represents instance methods of a class.

    from types import MethodType
    
    class MyClass:
        def my_method(self):
            pass
    
    obj = MyClass()
    print(isinstance(obj.my_method, MethodType))  # Output: True
    
  • UnboundMethodType: Represents unbound methods in Python 2 (not used in Python 3).

6. Traceback and Exception Types

  • TracebackType: Represents a traceback object.

    import sys
    from types import TracebackType
    
    try:
        1 / 0
    except ZeroDivisionError:
        tb = sys.exc_info()[2]
        print(isinstance(tb, TracebackType))  # Output: True
    

7. Ellipsis and NotImplemented Types

  • EllipsisType: Represents the Ellipsis object (...).

    from types import EllipsisType
    
    print(isinstance(..., EllipsisType))  # Output: True
    
  • NotImplementedType: Represents the NotImplemented object.

    from types import NotImplementedType
    
    print(isinstance(NotImplemented, NotImplementedType))  # Output: True
    

8. Other Specialized Types

  • MappingProxyType: Represents a read-only view of a dictionary.

    from types import MappingProxyType
    
    my_dict = {"a": 1}
    proxy = MappingProxyType(my_dict)
    print(isinstance(proxy, MappingProxyType))  # Output: True
    
  • SimpleNamespace: Represents a simple object for attribute storage.

    from types import SimpleNamespace
    
    obj = SimpleNamespace(a=1, b=2)
    print(isinstance(obj, SimpleNamespace))  # Output: True
    

Why Use The Types Module?

The types module is particularly useful for checking specific object types that aren't easily distinguishable using type() or isinstance(). It provides a way to identify Python's internal and specialized types, such as functions, generators, coroutines, and more.