2015-06-26 25 views
1

在Python中,我想有一個函數在不同的輸入類型上工作。這樣的事情:我如何正確地寫一個列表上的函數?

def my_square(x): 
    return x ** 2 

my_square(2)    #return 4 
my_square(range(10))  #should return a list [0 ... 81] 

npa = numpy.zeros(10) 
my_square(npa)   # should return a numpy array with the squares of zeros 

基本上,編寫函數爲標量和iterables的最佳做法是什麼?這可以用* args或* kwargs來完成嗎?

+7

爲什麼不寫的函數,在標量只有工作,然後如把它映射到迭代器上?你當然可以在函數內處理這兩個問題,但我會盡量避免它。 – jonrsharpe

+0

這是一個壞主意,做你想做的事情,除了在邊緣情況下。爲每種輸入類型編寫版本。 – Carcigenicate

+0

@jonrsharpe Numpy數組的設計使得可以編寫表達式,以確定內部使用的變量是標量還是數組。無論「x = 4」或「x」是一百萬條記錄的「ndarray」,OP的my_square(x)都可以同樣有效。在這種情況下,編寫一個接受標量或迭代函數的函數是許多科學軟件包中常用的數字python習慣用法,並且使用'map'將其應用於可迭代是一種反模式。但是,如果函數是一般的(並且不能利用numpy的向量化),那麼列表comp。或地圖是要走的路。 – jme

回答

0

一個典型的方法是使用numpy.asarray將函數的輸入轉換爲ndarray。如果輸入是標量,那麼結果是一個具有0維的數組,其行爲基本上像Python數字類型。例如:

def square(x): 
    x = np.asarray(x) 
    return x**2 

這樣:

>>> square(4) 
16 
>>> square([1, 2, 3, 4]) 
array([ 1, 4, 9, 16]) 

注意,我給一個列表作爲輸入,並收到了ndarray作爲輸出。如果你絕對必須作爲你的輸入提供接收相同類型的輸出,你可以返回之前的轉換結果:

def square(x): 
    in_type = type(x) 
    x = np.asarray(x) 
    return in_type(x**2) 

但這招致了一點好處的額外費用。

0

由於Python是動態類型的,並且Python的設計哲學違背了在調用函數時想要顯着差異的想法,所以這不被視爲Pythonic。但是,您可以使用isInstance()方法來完成您想要的操作,如果必須的話。例如:

def mySquare(x): 
    if isinstance(x, int): 
     return x**2 
    elif isinstance(x, range): 
     return [i ** 2 for i in x] 

print(mySquare(2)); //4 
print(mySquare(range(10))); //[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 

IDEOne link here.

然而,僅僅因爲你可以做一些事情,並不意味着你應該。

Refer to this question for further information on isinstance,我建議你看看Duck Typing以及。

此外,single dispatch function也可能提供您所需要的,但我沒有足夠的經驗來爲此提供解釋,但是,它可能是您想要查看的內容。

+0

這種'isinstance'方法非常脆弱 - 如果輸入是'float'呢?最好使用例如'collections.abc.Sequence',或鴨子打字('嘗試'迭代輸入,並假設它是標量,如果失敗)。 – jonrsharpe

+0

@jonrsharpe我同意,我試圖讓它適用於numpy.zeros,並且我無法獲得正確的結果,因爲如果它們不完全匹配,isinstance條件將不會執行。絕對unpythonic,但快速和骯髒。 – matrixanomaly

+1

您可以提供多個課程,例如'isinstance(x,(int,float))',但最終可能比它的價值更麻煩。 – jonrsharpe

0

這將是更好的做法,更容易維護,只是做的事:

def foo(n): 
    return n ** 2 

,然後在需要時建立名單,類型的字典,等(我不是超級熟悉numpy但我想有一些類似的,你可以這樣做):

foo_list = [foo(n) for n in range(10)] 
foo_dict = {n: foo(n) for n in range(10)} 

它似乎有使用numpy.fromiter()numpy的。從文檔:

iterable = (foo(n) for n in range(5)) 
foo_arr = np.fromiter(iterable, np.whatever_numpy_datatype) 

你甚至可以讓那些到功能,如果你真的需要:

def foo_list(start=0, stop=0): 
    return [foo(n) for n in range(start, stop)] 

def foo_dict(start=0, stop=0): 
    return {n: foo(n) for n in range(10)} 

另一種選擇,因爲它更容易請求原諒比許可:

def foo(scalar_or_iter): 
    try: 
     return [n ** 2 for n in scalar_or_iter] 
    except TypeError: 
     return scalar_or_iter ** 2 
+0

爲了得到NumPy數組'x'的元素的平方,你只需寫'x ** 2'。通過Python級別的迭代來平方NumPy數組會非常低效。 –

+0

因此,像這樣'foo_arr = np.asarray(some_list)'然後只是'foo_arr ** 2' @MarkDickinson這樣的數組會更有效率? – IanAuld

+0

也許吧。如果你真的從列表開始,你需要決定是否真的值得轉換爲數組。我的觀點是,如果你用NumPy數組開始*,那麼在Python級別迭代它的元素是沒有意義的(在這種情況下,不需要'asarray'調用)。 –

0

正如評論中所建議的那樣,只需編寫一個標量版本的函數,然後使用map來進行列表等,對於iterables使用imap(地圖不適用於這些):

map(myFunc,myList) 

import itertools 
itertools.imap(myFunc,myIterable) 
相關問題