这种情况在我的代码中经常发生。假设我有一个函数do_sth(a,b)
,只是为了这个例子,只需计算a+b
,a,b
或者1D numpy
数组或标量。在许多情况下,我需要函数来广播操作,因此如果a,b
都是1D数组,则结果将是2D数组。我的意思的一个例子如下:
do_sth(1,2) -> 3
do_sth([1,2],0) -> array([1, 2])
do_sth(0,[3,4]) -> array([3, 4])
do_sth([1,2],[3,4]) -> array([[4, 5], [5, 6]])
这有点类似于n ufunc
的行为。可能的实现如下:
from numpy import newaxis, atleast_1d
def do_sth(a, b):
"a,b should be either 1d numpy arrays or scalars"
a, b = map(atleast_1d, [a, b])
# the line below mocks a more complicated calculation
res = a[:, newaxis] + b[newaxis]
conds = [a.size == 1, b.size == 1]
if all(conds):
return res[0, 0]
elif any(conds):
return res.ravel()
else:
return res
正如您所看到的,有很多样板。第一个问题是:这是进行此输入/输出转换的正确方法吗?有没有理由不使用装饰器来处理这样的情况?有没有关于此事的指导原则?
此外,如果a
或b
是具有2D,3D形状的numpy数组,则通过添加模拟的更复杂的计算通常会严重失败。我说计算失败的意义不明显,或者可能在代码的不同版本中随时间变化,并且很难看出错误和输入形状错误之间的联系。我认为不建议将复杂的计算放在try/except
块中(在python EAFP之后)。在这种情况下,检查函数开头的2个数组的形状是否正确?还有其他选择吗?是否有一个numpy函数允许同时将输入转换为numpy数组,并检查输入是否与一定数量的维度兼容,如asarray_withdim(arr,ndim=5)
?
答案 0 :(得分:1)
关于装饰器的使用 - 我在numpy
代码中没有看到很多装饰器的使用,但我认为这是因为大多数功能是在装饰器在Python中变得普遍之前开发的。如果你可以使它工作,不应该有任何缺点(但我不是装饰者或ufunc
的专家。)
非编译的numpy函数通常有很多代码可以将输入按摩到方便的维度。然后他们做核心动作,然后进行最终重塑和打包。他们可能会使用像np.atleast_2d这样的函数来确保有足够的尺寸,并使用.reshape(-1,1,1)来压缩多余的尺寸。
np.tensordot
是一个在输入上执行轴转置和重新整形的示例,因此它可以应用已编译的np.dot
。 np.insert
从一系列ndim
和isinstance
测试开始。特殊情况要尽早处理,而一般情况则留到最后。编译了np.einsum
,但是在最终创建nditer
对象并进行计算之前,在C代码中进行了大量的预处理。