Function that generates steps time series from user given values I created simple function, that generate values of series representing repeating sequence of steps in time. User can define:


step values
width of the steps
how many times the step sequence should repeat
or the size of returned series


If the size is not defined, the size of returned data should be determined by number of repeats.

So the call 

steps(2, [1, 2, 3], repeat=2)


should return

[1 1 2 2 3 3 1 1 2 2 3 3]


The function follows

def steps(step_width, values, repeat=1, size=None):
    """
    This function generates steps from given values.

    **Args:**

    * `step_width` - desired width of every step (int)

    * `values` - values for steps (1d array) 

    **Kwargs:**

    * `repeat` - number of step sequence repetions (int), this value is used,
      if the `size` is not defined

    * `size` - size of output data in samples (int), if the `size` is used,
      the `repeat` is ignored.

    **Returns:**

    * array of values representing desired steps (1d array)
    """
    try:
        step_width = int(step_width)
    except:
        raise ValueError('Step width must be an int.') 
    try:
        repeat = int(repeat)
    except:
        raise ValueError('Repeat arg must be an int.') 
    try:
        values = np.array(values)
    except:
        raise ValueError('Values must be a numpy array or similar.') 
    # generate steps
    x = np.repeat(values, step_width)          
    if size is None:
        # repeat according to the desired repetions
        x_full = np.tile(x, repeat)      
    else:
        try:
            repeat = int(repeat)
        except:
            raise ValueError('Repeat arg must be an int.') 
        # repeat till size is reached and crop the data to match size
        repeat = int(np.ceil(size / float(len(x))))
        x_full = np.tile(x, repeat)[:size]
    return x_full


I would appreciate any feedback. Especially I am not sure about effectivity of the error raising as it is implemented right now.
Your type checks are distracting, they are probably better extracted to a separate function.

def check_type_or_raise(obj, expected_type, obj_name):
    if not isinstance(obj, expected_type):
        raise TypeError(
            "'{}' must be {}, not {}".format(
                obj_name,
                expected_type.__name,
                obj.__class__.__name__)


Note that this has slightly different behaviour than your code: you would e.g. accept a step_width of 2.5 and convert it to a 2. This is typically not what you want, so I think it is better to raise for such cases.

If you don't mind the conversion, then you probably don't need to wrap your calls to int() in a try and re-raise, as you will already get a nice error, e.g.:

&gt;&gt;&gt; int('abc')
ValueError: invalid literal for int() with base 10: 'abc'


I would also refactor your code to have the output creation happen in a single, common place, after some manipulation of the arguments:

def steps(step_width, values, repeat=1, size=None):
    check_type_or_raise(step_width, int, 'step_width')
    check_type_or_raise(repeat, int, 'repeat')
    values = np.asarray(values)
    if size is not None:
        check_type_or_raise(size, int, 'size')
        # This does rounded up integer division without floating point
        repeat = (size - 1) // (len(values) * step_width) + 1
    return np.tile(np.repeat(values, step_width), repeat)[:size]