def batch_loader(*args, batch_size=None, num_batches:int=10):
    """
    Fast batch loader without collate_fn
    
    Parameters
    ----------
    - :attr:`*args`: `torch.tensor` or `numpy.array` \n
        The first dimension (`shape[0]`) of all input must be the same
    - :attr:`batch_size`: int | None \n
        The number of batch size. If it equals to `None`, the `batch_size` will be calculated from `num_batches`
    - :attr:`num_batches`: int \n
        The total number of batches (default=10). If `batch_size` is not `None`, `num_batches` will be ignored.

    Returns
    -------
    Generator of tuples of a batch

    Example
    -------
    ```python
    >> bl = batch_loader(np.ones((100,3)), np.ones(100), batch_size=30)
    >> for x, y in bl:
    >>    print(x.shape, y.shape)
    (30, 3) (30,)
    (30, 3) (30,)
    (30, 3) (30,)
    (10, 3) (10,)
    ```
    """
    assert len(args) > 0, 'Missing input'
    assert all([hasattr(x, 'shape') for x in args]), 'arguments must be torch.tensor or numpy.array'
    total_length = [x.shape[0] for x in args]
    assert total_length.count(total_length[0]) == len(total_length), f'The first dimension of every tensor or array must be the same: {total_length}'
    
    if batch_size is None:
        batch_size = total_length[0] // num_batches

    return (
        tuple(x[start:start+batch_size] for x in args) for start in range(0, total_length[0], batch_size)
    )