Here's one way you could do it. The function get_shape(a) is a recursive function that returns either the shape of a as a tuple, or None if a does not have a regular shape (i.e. a is ragged). The tricky part is actually the function is_scalar(a): we want string and bytes instances, and arbitrary noniterable objects (such as None, or Foo() where Foo is class Foo: pass) to be considered scalars. np.iscalar() does some of the work; an attempt to evaluate len(a) does the rest. The NumPy docs suggest the simple expression np.ndim(a) == 0, but that will invoke the array creation for a, which will trigger the warning if a is ragged. (The is_scalar function might miss some cases, so test it carefully with typical data that you use.)
import numpy as np
def is_scalar(a):
if np.isscalar(a):
return True
try:
len(a)
except TypeError:
return True
return False
def get_shape(a):
"""
Returns the shape of `a`, if `a` has a regular array-like shape.
Otherwise returns None.
"""
if is_scalar(a):
return ()
shapes = [get_shape(item) for item in a]
if len(shapes) == 0:
return (0,)
if any([shape is None for shape in shapes]):
return None
if not all([shapes[0] == shape for shape in shapes[1:]]):
return None
return (len(shapes),) + shapes[0]
def is_ragged(a):
return get_shape(a) is None
For example,
In [114]: is_ragged(123)
Out[114]: False
In [115]: is_ragged([1, 2, 3])
Out[115]: False
In [116]: is_ragged([1, 2, [3, 4]])
Out[116]: True
In [117]: is_ragged([[[1]], [[2]], [[3]]])
Out[117]: False
In [118]: is_ragged([[[1]], [[2]], [[3, 99]]])
Out[118]: True