新手請教python問題,就是說像map,zip,filter這些函數都返回單次迭代器。?

為什麼不設計成返回iterable來支持多個迭代器呢?有的設計成iterable,有的設計成iterator。它們的思路是什麼呢?全部設計成iterable不好嗎


因為做不到返回 iterable

iterable 要求實現 __getitem__,而 python3 以後 map,filter,zip 等等都是 lazy 的,硬要 getitem 也是走到哪兒算到哪兒完全沒有保存之前結果,比如 [2]以後就再也拿不到 [1] 了、很蠢。其實還蠻統一的,itertools 里返回的都是 iterator

需要 getitem 的場合請手動轉成 list

—-

我發現我的描述有點問題,建議去看靈劍的答案補充,核心其實不是能不能 getitem,而是能不能多次迭代


python2的時候就是返回列表的,python3改成單次迭代器,目標是讓它實現按需計算,從而節約內存。因為輸入也可以是單次生成器,要能保證多次迭代不出問題,就只能整個緩存下來,這樣很浪費。

另外單次還是多次迭代和__getitem__關係不大……set也是多次迭代的


不好,原因靈劍已經說了。想獲得多個用tee就行了

https://docs.python.org/3/library/itertools.html#itertools.tee


我儘力試圖理解題主的意思。其實iterator是一種iterable。這裡iterable特指__getitem__那類隨機讀取的。

py3的map等等返回的迭代器是用到哪裡生成到哪裡的。這保障了性能避免額外開銷,同時也支持了itertools下面的無窮的迭代器。這也意味著不可能先全部生成完一個可迭代的對象再迭代它。(當然硬返回個只支持__iter__的對象也實在沒啥意義,又不能隨機訪問地得到元素)

而另一方面如果想從有窮iterator里生成支持隨機訪問的iterable很簡單。list一下就行。

額外的開銷,需要顯示的額外操作,這設計很合理。

PS generator是個很有趣的iterator,可以寫出各種騷操作。(當然深入下去,就會發現這裡的執行流很奇葩,像剛才說的用到哪兒生成到哪兒,generator並不是普通的函數,它是一個獨立的執行流,彷彿是一個獨立的線程,但彼此之間不是多線程那樣競爭執行的,這就是協程)


有的初學者常見,過度鑽牛角尖,不照著庫的文檔寫,反而琢磨標準庫為啥要這樣設計的問題。

黃哥Python提醒, 有的問題只有你會C 語言編程,懂編譯原理才能整明白的東西,

所以不要過度陷入語法細節,鑽牛角尖。

糾正你的錯誤認識,iterable 是可迭代的的意思。

這樣表示,這個對象是可迭代對象。定義了__iter__ 或__getitem__ 方法的對象是iterable對象

iterator 是迭代器的意思

Iteration is a general term for taking each item of something, one after another. Any time you use a loop, explicit or implicit, to go over a group of items, that is iteration.

In Python, iterable and iterator have specific meanings.

An iterable is an object that has an __iter__ method which returns an iterator, or which defines a __getitem__ method that can take sequential indexes starting from zero (and raises an IndexError when the indexes are no longer valid). So an iterable is an object that you can get an iterator from.

An iterator is an object with a next (Python 2) or __next__ (Python 3) method.

Whenever you use a for loop, or map, or a list comprehension, etc. in Python, the next method is called automatically to get each item from the iterator, thus going through the process of iteration.

A good place to start learning would be the iterators section of the tutorial and the iterator types section of the standard types page. After you understand the basics, try the iterators section of the Functional Programming HOWTO.

list 是可迭代的,但不是迭代器,可以用iter函數將list轉換為迭代器。

提問者的問題就像下面代碼描述的

就是list等可以用for 循環反覆迭代,迭代器不能從頭反覆迭代。你描述的問題描述的不清楚,因為你看的書上「多個迭代器」,我覺得是翻譯的問題。


對於map來說, 真的要看情況的。

某人在學習Python函數的時候, 介紹map()函數的使用:

def sqr(x):
return x ** 2

a = [1, 2, 3]
print map(sqr, a)

這個map函數是python的內嵌的函數, 那麼如何手寫一個自己的map函數, 實現內嵌map函數一模一樣的功能呢?

第一式: 不固定參數

Python內嵌的map函數允許不固定參數, 如下圖所示, map第一個參數是一個函數, 剩餘的不固定參數只要iterative類型就可以。 譬如,下面可以是元組和數組。

def add(x, y):
return x + y

a = (2, 3, 4)
b = [10, 5, 3]
print map(add, a, b)

那麼如何實現這個不固定參數的讀取呢? Python給了*var的形式,可以接受任意參數。 某人根據map的功能進行設計, 先將之後的參數按行拼接成參數, 然後調用函數,得到返回值。

於是,某人實現了如下函數:

def my_map(func, *lst):
res = []
m = len(lst)
n = len(lst[0])
for i in range(n):
args = []
for j in range(m):
args.append(lst[j][i])
res.append(func(*args))
return res

跑一下, 杠杠的!和map()結果一致。

但是, 這個是不是內嵌map的全部實現呢?

第二式: 對齊Iterator,返回默認值

所謂天大差別, 不過一橫, 就算差了一點, 任然差別太大。 所以在比較my_map和內嵌的map的時候,是不是還要邊界檢查。 前面測試了相同長度的數組的map, 對於不同長度的數組的map呢?

a = (2, 3, 4)
b = [10, 5, 3]

print map(lambda x,y: "{}~{}".format(x,y), a, b+[0])
print my_map(lambda x,y: "{}~{}".format(x,y), a, b+[0])

可以上面結構看到, 內嵌的map會按最大長度自動補齊。

那麼一種實現,就是找到數據裡面最長的, 將所有不夠長的補齊None值,之後再調用my_map()

另外一種,我就是利用iterator到結束之後, 默認返回None值。默認的next()函數的第二個參數,可以是返回的默認值。

def my_map2(func, *lst):
res = []
its = [iter(it) for it in lst]
ln = max([len(it)for it in lst])
while ln &> 0:
args = [next(it,None) for it in its]
res.append(func(*args))
ln -= 1
return res
a = (2, 3, 4)
b = [10, 5, 3]

print map(lambda x,y: "{}~{}".format(x,y), a, b+[0])
print my_map2(lambda x,y: "{}~{}".format(x,y), a, b+[0])

效果不錯, 那麼是不是就搞定了呢?

第三式: Python 2,3兼容,內部類型變換

是不是上面實現的my_map2(),就是一個python2和python3下都和map函數一致呢? 前面,我們在使用print函數的時候, 沒有使用括弧, 那麼明顯是在Python2的環境下面, 那麼對於Python3環境下呢?在Python3下測試一下吧:

我們發現列印了是一個map object。嘿, 內嵌map函數的實現, 在Python2和Python3中似乎很不一樣。 如果我們試圖通過subscribe[]來訪問的&

的時候,發現這個map並不可以通過index來訪問。

那麼基本判斷就是一個iterative的對象。 但是如何列印具體內容出來看呢? 最好是通過Python的內部變數之間轉換進行list(&

)

這樣, 那麼我們再次調用非對齊的map來測試:

print (list(map(lambda x,y: "{}~{}".format(x,y), a, b+[0])))

我們發現在Python3裡面不再按照最長的iteration來進行對齊了, 而是按照最短的來對齊。 因此, 我們可以把最長換成最短來模擬Python3中的map。

def my_map3(func, *lst):
res = []
its = [iter(it) for it in lst]
ln = min([len(it)for it in lst])
while ln &> 0:
args = [next(it,None) for it in its]
res.append(func(*args))
ln -= 1
return res

是不是my_map3()在Python3中,就是最好的模擬呢?

第四式: 對齊Iterator,返回默認值

但是畢竟Python3中返回的是一個iterator,一個差異就是, 我們返回一個數組(數組可以通過subscribe[]訪問), 我們知道iterator本身,可以通過generator來進行簡化, 並且這種lazy實現, 會提高調用效率。 這時候偉大的yield關鍵詞上場了。

一般來說,在Python中, 如何用好yield關鍵詞和decorator功能,都會極大提高工作效率的,也是反應一個人Python功底的東東。

def my_map4(func, *lst):
its = [iter(it) for it in lst]
ln = min([len(it)for it in lst])
while ln &> 0:
args = [next(it,None) for it in its]
yield func(*args)
ln -= 1

至此 , 我們可以列印出來,看my_map4返回的也是一個iterative object,這個generator object和map object有著異曲同工之妙。 另外再通過list進行內部變數轉化,看到它實現的功能也一致。

至此, 某人聽完第三式和第四式的實現, 對Python大大失去興趣,坑太多! ! !

小結:

本人設計的一個面試題,並且自我回答,通過對Python2和Python3中map函數的測試, 試圖自己實現這個內嵌函數, 經驗有限,希望大家對map有更為深入的了解,尤其Python2和Python3中實現的巨大差異。


瀉藥

我這個自稱「精通cpython解釋器底層」的人連迭代器是啥都不知道

真的對不起

是我太垃圾了

拖你們後腿了


推薦閱讀:

像我這種程序員還有必要繼續做下去嗎?
如何看待中國編程/演算法教育總是教一些沒用的、無意義的古老的東西,不涉及語言新標準?
選擇編程語言對初學者有多大幫助?
epoll編程,如何實現高並發伺服器開發?
什麼人適合當程序員?

TAG:Python | 編程 | Python入門 |