我正在測驗Python的Pipe庫,它試圖簡化可迭代物件的處理,基本上允許撰寫
ys = xs | f1 | f2 | f3
代替
ys = f3(f2(f1(xs))
在Pipe類本身很短:遇到時只需呼叫包裝的功能__ror__操作:
import functools
class Pipe:
def __init__(self, function):
self.function = function
functools.update_wrapper(self, function)
def __ror__(self, other):
return self.function(other)
def __call__(self, *args, **kwargs):
return Pipe(lambda x: self.function(x, *args, **kwargs))
但是,我發現了一個輸出不符合預期的特殊情況:
import numpy as np
from pipe import Pipe
y1 = np.array([1, 2, 3]) | Pipe(np.median)
y2 = [1, 2, 3] | Pipe(np.median)
print(y1) # [1.0 2.0 3.0]
print(y2) # 2.0
在這兩種情況下,我都假設它np.median應用于可迭代物件,但是當輸入是一個 numpy 陣列時,函式會映射到陣列上,而不是使陣列成為實際引數(就像使用普通串列時的情況一樣) )。也就是說,在__ror__方法中,other引數是陣列中的一個專案,而不是實際的陣列本身。我不明白這怎么會發生。這是一些麻木的怪癖嗎?
這是完整的可重現示例。
我在專案的 repo 中沒有發現任何相關問題。
uj5u.com熱心網友回復:
“我不明白這怎么會發生。這是一些麻木的怪癖嗎?”
NumPy 有廣播。使用np.array([1, 2, 3])左側的陣列,__or__NumPy 陣列的方法將Pipe(np.median)它作為other引數獲取的物件視為標量,并廣播它以使其行為像[Pipe(np.median), Pipe(np.median), Pipe(np.median)](即,使 的形狀other與 的形狀匹配self)。
由于Pipe(np.median)不是 NumPy 已知的型別,因此other引數被視為object陣列,并且__or__操作將嘗試對輸入進行元素操作。這意味著最終會呼叫的__ror__方法Pipe(np.median),但它會在np.array([1, 2, 3])和 中的每一對中發生[Pipe(np.median), Pipe(np.median), Pipe(np.median)]。這就是為什么您將結果應用于每個元素,而不是np.median作為一個整體應用于左側的陣列。這也是結果的資料型別object而不是 NumPy 浮點型別的原因:
In [19]: np.array([1, 2, 3]) | Pipe(np.median)
Out[19]: array([1.0, 2.0, 3.0], dtype=object)
關于實作 的類的處理,__or__Pipe 的作者說“這是 的極限Pipe,沒有干凈的方法可以克服這個問題。” 另請參閱如果左側物件定義,則管道不起作用__or__。
物有所值...
允許Pipe使用 NumPy 陣列(或實作 的任何其他物件__or__)的問題中給出的代碼的修改是提供一個選項,在資料被發送到管道之前,在資料周圍放置一個薄包裝器。如果 的__ror__方法Pipe看到這個包裝器,它會解開資料,呼叫它的函式,并在回傳之前重新包裝結果。需要管道末端的最后一步來解包被包裝的資料。也許是這樣的:
import functools
class Wrap:
def __init__(self, obj):
self._object = obj
def unwrap(self):
return self._object
class Pipe:
def __init__(self, function):
self.function = function
functools.update_wrapper(self, function)
def __ror__(self, other):
if isinstance(other, Wrap):
return Wrap(self.function(other.unwrap()))
else:
return self.function(other)
def __call__(self, *args, **kwargs):
return Pipe(lambda x: self.function(x, *args, **kwargs))
class Unwrap:
def __ror__(self, other):
if isinstance(other, Wrap):
return other.unwrap()
else:
return other
例如,
In [13]: a = np.array([1, 2, 3])
In [14]: Wrap(a) | Pipe(np.median) | Unwrap()
Out[14]: 2.0
In [15]: b = [1, 2, 3]
In [16]: Wrap(b) | Pipe(np.median) | Unwrap()
Out[16]: 2.0
One more follow-up...
If it is acceptable to require a wrapper at the input of the pipe and an unwrapper at the output, the pipe mechanism can be changed to allow arbitrary callables in the pipe without being wrapped in a Pipe class:
# A token to indicate the end of a pipe.
ExitPipe = None
class EnterPipe:
def __init__(self, obj):
self._object = obj
def __or__(self, other):
if other == ExitPipe:
return self._object
if not callable(other):
raise TypeError(f"pipe element '{other}' is not callable")
return EnterPipe(other(self._object))
For example,
In [10]: a = np.array([1, 2, 3])
In [11]: EnterPipe(a) | np.sin | np.cos | max | ExitPipe
Out[11]: 0.9900590857598653
In [12]: max(np.cos(np.sin(a)))
Out[12]: 0.9900590857598653
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/345551.html
