def num_qubits(val: Any, default: TDefault = RaiseTypeErrorIfNotProvidedInt) -> int | TDefault:
    """Returns the number of qubits, qudits, or qids `val` operates on.

    Args:
        val: The value to get the number of qubits from.
        default: Determines the fallback behavior when `val` doesn't have
            a number of qubits. If `default` is not set, a TypeError is raised.
            If default is set to a value, that value is returned.

    Returns:
        If `val` has a `_num_qubits_` method and its result is not
        NotImplemented, that result is returned. Otherwise, if `val` has a
        `_qid_shape_` method, the number of qubits is computed from the length
        of the shape and returned e.g. `len(shape)`. If neither method returns a
        value other than NotImplemented and a default value was specified, the
        default value is returned.

    Raises:
        TypeError: `val` doesn't have either a `_num_qubits_` or a `_qid_shape_`
            method (or they returned NotImplemented) and also no default value
            was specified.
    """
    num_getter = getattr(val, '_num_qubits_', None)
    num_qubits = NotImplemented if num_getter is None else num_getter()
    if num_qubits is not NotImplemented:
        return num_qubits

    # Fallback to _qid_shape_
    getter = getattr(val, '_qid_shape_', None)
    shape = NotImplemented if getter is None else getter()
    if shape is not NotImplemented:
        return len(shape)

    # Check if val is a list of qids
    if isinstance(val, Sequence) and all(isinstance(q, ops.Qid) for q in val):
        return len(val)

    if default is not RaiseTypeErrorIfNotProvidedInt:
        return default

    if num_getter is not None:
        raise TypeError(
            f"object of type '{type(val)}' does have a _num_qubits_ method, "
            "but it returned NotImplemented."
        )
    if getter is not None:
        raise TypeError(
            f"object of type '{type(val)}' does have a _qid_shape_ method, "
            "but it returned NotImplemented."
        )
    raise TypeError(f"object of type '{type(val)}' has no _num_qubits_ or _qid_shape_ methods.")
Returns the number of qubits, qudits, or qids `val` operates on.

    Args:
        val: The value to get the number of qubits from.
        default: Determines the fallback behavior when `val` doesn't have
            a number of qubits. If `default` is not set, a TypeError is raised.
            If default is set to a value, that value is returned.

    Returns:
        If `val` has a `_num_qubits_` method and its result is not
        NotImplemented, that result is returned. Otherwise, if `val` has a
        `_qid_shape_` method, the number of qubits is computed from the length
        of the shape and returned e.g. `len(shape)`. If neither method returns a
        value other than NotImplemented and a default value was specified, the
        default value is returned.

    Raises:
        TypeError: `val` doesn't have either a `_num_qubits_` or a `_qid_shape_`
            method (or they returned NotImplemented) and also no default value
            was specified.
    
cirq-core/cirq/protocols/qid_shape_protocol.py