numpy.vectorize
を使用すると、入力として単一の数値を期待する「通常の」関数を、入力のリストを次のリストに変換できる関数に変換できることがわかりました。関数は各入力にマップされています。たとえば、次のテストに合格します。
import numpy as np
import pytest
@np.vectorize
def f(x):
if x == 0:
return 1
else:
return 2
def test_1():
assert list(f([0, 1, 2])) == [1, 2, 2]
def test_2():
assert f(0) == 1
if __name__ == "__main__":
pytest.main([__file__])
ただし、インスタンス属性を使用するインスタンスメソッドでこれを機能させることはできませんでした。例えば:
class Dummy(object):
def __init__(self, val=1):
self.val = val
@np.vectorize
def f(self, x):
if x == 0:
return self.val
else:
return 2
def test_3():
assert list(Dummy().f([0, 1, 2])) == [1, 2, 2]
このテストは失敗します:
=================================== FAILURES ===================================
____________________________________ test_3 ____________________________________
def test_3():
> assert list(Dummy().f([0, 1, 2])) == [1, 2, 2]
test_numpy_vectorize.py:31:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/numpy/lib/function_base.py:2739: in __call__
return self._vectorize_call(func=func, args=vargs)
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/numpy/lib/function_base.py:2809: in _vectorize_call
ufunc, otypes = self._get_ufunc_and_otypes(func=func, args=args)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <numpy.lib.function_base.vectorize object at 0x106546470>
func = <function Dummy.f at 0x10653a2f0>, args = [array([0, 1, 2])]
def _get_ufunc_and_otypes(self, func, args):
"""Return (ufunc, otypes)."""
# frompyfunc will fail if args is empty
if not args:
raise ValueError('args can not be empty')
if self.otypes is not None:
otypes = self.otypes
nout = len(otypes)
# Note logic here: We only *use* self._ufunc if func is self.pyfunc
# even though we set self._ufunc regardless.
if func is self.pyfunc and self._ufunc is not None:
ufunc = self._ufunc
else:
ufunc = self._ufunc = frompyfunc(func, len(args), nout)
else:
# Get number of outputs and output types by calling the function on
# the first entries of args. We also cache the result to prevent
# the subsequent call when the ufunc is evaluated.
# Assumes that ufunc first evaluates the 0th elements in the input
# arrays (the input values are not checked to ensure this)
args = [asarray(arg) for arg in args]
if builtins.any(arg.size == 0 for arg in args):
raise ValueError('cannot call `vectorize` on size 0 inputs '
'unless `otypes` is set')
inputs = [arg.flat[0] for arg in args]
> outputs = func(*inputs)
E TypeError: f() missing 1 required positional argument: 'x'
numpy.vectorize
をインスタンスメソッドに適用することは可能ですか?
インスタンスのメソッドで直接np.vectorize
を使用できます。
class Dummy(object):
def __init__(self, val=1):
self.val = val
def f(self, x):
if x == 0:
return self.val
else:
return 2
vec_f = np.vectorize(Dummy().f)
def test_3():
assert list(vec_f([0, 1, 2])) == [1, 2, 2]
test_3()
vec_f
にベクトル化された関数__init__
を作成することもできます。
class Dummy(object):
def __init__(self, val=1):
self.val = val
self.vec_f = np.vectorize(self.f)
def f(self, x):
if x == 0:
return self.val
else:
return 2
def test_3():
assert list(Dummy().vec_f([0, 1, 2])) == [1, 2, 2]
または別の命名スキームで:
class Dummy(object):
def __init__(self, val=1):
self.val = val
self.f = np.vectorize(self.scalar_f)
def scalar_f(self, x):
if x == 0:
return self.val
else:
return 2
def test_3():
assert list(Dummy().f([0, 1, 2])) == [1, 2, 2]
test_3()
test_3()
メソッドのベクトル化された実装を使用する場合は、次のようにexcluded
パラメーターを使用できます。
class MyClass:
def __init__(self, data):
self.data = data
self.my_vectorized_func = np.vectorize(self.my_func, excluded='self')
def my_func(self, x):
return pow(x, self.data)
これにより、ベクトル化されていないメソッドのようにメソッドを使用できます。
In[1]: myclass = MyClass(3) # '3' will be the power factor of our function
In[2]: myclass.my_vectorized_func([1, 2, 3, 4, 5])
Out[3]: array([ 1, 8, 27, 64, 125])
memoized
デコレータで見たテクニックを思い出し、次のようにnumpy.vectorize
をサブクラス化することで、デコレータをインスタンスメソッドでも機能させることができました。
import numpy as np
import functools
class vectorize(np.vectorize):
def __get__(self, obj, objtype):
return functools.partial(self.__call__, obj)
ここで、Dummy
class'f
メソッドをnp.vectorize
ではなくvectorize
で装飾すると、テストに合格します。
class Dummy(object):
def __init__(self, val=1):
self.val = val
@vectorize
def f(self, x):
if x == 0:
return self.val
else:
return 2
def test_3():
assert list(Dummy().f([0, 1, 2])) == [1, 2, 2]
if __name__ == "__main__":
pytest.main([__file__])
出力付き
test_numpy_vectorize.py .
=========================== 1 passed in 0.01 seconds ===========================
[Finished in 0.7s]
これは、インスタンスメソッドと関数で動作する一般的なデコレータです(otypes
とsignature
については Numpyのドキュメント を参照してください)。
_from functools import wraps
import numpy as np
def vectorize(otypes=None, signature=None):
"""Numpy vectorization wrapper that works with instance methods."""
def decorator(fn):
vectorized = np.vectorize(fn, otypes=otypes, signature=signature)
@wraps(fn)
def wrapper(*args):
return vectorized(*args)
return wrapper
return decorator
_
これを使用して、次のようにメソッドをベクトル化できます。
_class Dummy(object):
def __init__(self, val=1):
self.val = val
@vectorize(signature="(),()->()")
def f(self, x):
if x == 0:
return self.val
else:
return 2
def test_3():
assert list(Dummy().f([0, 1, 2])) == [1, 2, 2]
_
重要なのは、signature
kwargを利用することです。 _->
_の左側の括弧で囲まれた値は入力パラメーターを指定し、右側の値は出力値を指定します。 _()
_はスカラー(0次元ベクトル)を表します。 _(n)
_は1次元ベクトルを表します。 _(m,n)
_は2次元ベクトルを表します。 _(m,n,p)
_は3次元ベクトルを表します。ここで、signature="(),()->()"
は、最初のパラメーター(self
)がスカラーであり、2番目のパラメーター(x
)もスカラーであることをNumpyに指定し、メソッドはスカラー(x
に応じて、_self.val
_または_2
_のいずれか)。
_$ pytest /tmp/instance_vectorize.py
======================= test session starts ========================
platform linux -- Python 3.6.5, pytest-3.5.1, py-1.5.3, pluggy-0.6.0
rootdir: /tmp, inifile:
collected 1 item
../../tmp/instance_vectorize.py . [100%]
==================== 1 passed in 0.08 seconds ======================
_
docs から:
ベクトル化された出力のデータ型は、入力の最初の要素で関数を呼び出すことによって決定されます。これは、otypes引数を指定することで回避できます。
関数f(self, x)
の最初の入力はself
です。たぶん、その関数をstaticmethod
関数のラッパーにすることができますか?