# Buggy code
def wrapper(*args, **kwargs):
    bound_args = signature(func).bind(*args, **kwargs)

    # Auto-broadcast select xarray arguments, and update bound_args
    if broadcast is not None:
        arg_names_to_broadcast = tuple(
            arg_name
            for arg_name in broadcast
            if arg_name in bound_args.arguments
            and isinstance(bound_args.arguments[arg_name], (xr.DataArray, xr.Variable))
        )
        broadcasted_args = xr.broadcast(
            *(bound_args.arguments[arg_name] for arg_name in arg_names_to_broadcast)
        )
        for i, arg_name in enumerate(arg_names_to_broadcast):
            bound_args.arguments[arg_name] = broadcasted_args[i]

    # Cast all Variables to their data and warn
    # (need to do before match finding, since we don't want to rewrap as Variable)
    for arg_name in bound_args.arguments:
        if isinstance(bound_args.arguments[arg_name], xr.Variable):
            warnings.warn(
                f"Argument {arg_name} given as xarray Variable...casting to its data. "
                "xarray DataArrays are recommended instead."
            )
            bound_args.arguments[arg_name] = bound_args.arguments[arg_name].data

    # Obtain proper match if referencing an input
    match = list(wrap_like) if isinstance(wrap_like, tuple) else wrap_like
    if isinstance(wrap_like, str):
        match = bound_args.arguments[wrap_like]
    elif isinstance(wrap_like, tuple):
        for i, arg in enumerate(wrap_like):
            if isinstance(arg, str):
                match[i] = bound_args.arguments[arg]

    # Cast all DataArrays to Pint Quantities
    for arg_name in bound_args.arguments:
        if isinstance(bound_args.arguments[arg_name], xr.DataArray):
            bound_args.arguments[arg_name] = bound_args.arguments[
                arg_name
            ].metpy.unit_array

    # Optionally cast all Quantities to their magnitudes
    if to_magnitude:
        for arg_name in bound_args.arguments:
            if isinstance(bound_args.arguments[arg_name], units.Quantity):
                bound_args.arguments[arg_name] = bound_args.arguments[arg_name].m

    # Evaluate inner calculation
    result = func(*bound_args.args, **bound_args.kwargs)

    # Wrap output based on match and match_unit
    if match is None:
        return result
    else:
        if match_unit:
            wrapping = _wrap_output_like_matching_units
        else:
            wrapping = _wrap_output_like_not_matching_units

        if isinstance(match, list):
            return tuple(wrapping(*args) for args in zip(result, match))
        else:
            return wrapping(result, match)


# Traceback (ValueError)
Traceback (most recent call last):
File "moist_4panel.py", line 168, in <module>
pw[x,y] = mpcalc.precipitable_water(ds['P'].isel(latitude=x,longitude=y),ds['TD'].isel(latitude=x,longitude=y),bottom=pbot,top=ptop).m
File "/d1/anaconda3/envs/era5/lib/python3.8/site-packages/metpy/xarray.py", line 1174, in wrapper
return wrapping(result, match)
File "/d1/anaconda3/envs/era5/lib/python3.8/site-packages/metpy/xarray.py", line 1214, in _wrap_output_like_not_matching_units
xr.DataArray(result, coords=match.coords, dims=match.dims) if output_xarray
File "/d1/anaconda3/envs/era5/lib/python3.8/site-packages/xarray/core/dataarray.py", line 344, in __init__
coords, dims = _infer_coords_and_dims(data.shape, coords, dims)
File "/d1/anaconda3/envs/era5/lib/python3.8/site-packages/xarray/core/dataarray.py", line 121, in _infer_coords_and_dims
raise ValueError(
ValueError: different number of dimensions on data and dims: 0 vs 1

# Fixed code
def wrapper(*args, **kwargs):
    bound_args = signature(func).bind(*args, **kwargs)

    # Auto-broadcast select xarray arguments, and update bound_args
    if broadcast is not None:
        arg_names_to_broadcast = tuple(
            arg_name
            for arg_name in broadcast
            if arg_name in bound_args.arguments
            and isinstance(bound_args.arguments[arg_name], (xr.DataArray, xr.Variable))
        )
        broadcasted_args = xr.broadcast(
            *(bound_args.arguments[arg_name] for arg_name in arg_names_to_broadcast)
        )
        for i, arg_name in enumerate(arg_names_to_broadcast):
            bound_args.arguments[arg_name] = broadcasted_args[i]

    # Cast all Variables to their data and warn
    # (need to do before match finding, since we don't want to rewrap as Variable)
    def cast_variables(arg, arg_name):
        warnings.warn(
            f"Argument {arg_name} given as xarray Variable...casting to its data. "
            "xarray DataArrays are recommended instead."
        )
        return arg.data

    _mutate_arguments(bound_args, xr.Variable, cast_variables)

    # Obtain proper match if referencing an input
    match = list(wrap_like) if isinstance(wrap_like, tuple) else wrap_like
    if isinstance(wrap_like, str):
        match = bound_args.arguments[wrap_like]
    elif isinstance(wrap_like, tuple):
        for i, arg in enumerate(wrap_like):
            if isinstance(arg, str):
                match[i] = bound_args.arguments[arg]

    # Cast all DataArrays to Pint Quantities
    _mutate_arguments(bound_args, xr.DataArray, lambda arg, _: arg.metpy.unit_array)

    # Optionally cast all Quantities to their magnitudes
    if to_magnitude:
        _mutate_arguments(bound_args, units.Quantity, lambda arg, _: arg.m)

    # Evaluate inner calculation
    result = func(*bound_args.args, **bound_args.kwargs)

    # Wrap output based on match and match_unit
    if match is None:
        return result
    else:
        if match_unit:
            wrapping = _wrap_output_like_matching_units
        else:
            wrapping = _wrap_output_like_not_matching_units

        if isinstance(match, list):
            return tuple(wrapping(*args) for args in zip(result, match))
        else:
            return wrapping(result, match)