你是如何深入理解 Python 的 list comprehension 就是 generator 這一點的?
此外map filter 得到的是iterator. 這一點設計的哲學如何理解呢?
另外generator 其實是generator iterators. 返回的是一個iterators . 但自己卻是一個function. iterator 卻是一個object . Python 設計的深處水好深啊
一句句回答你的疑問
就是使用了 iterator 模式啊,可以更介面化的拿數據,不暴露內部實現,容易解耦,同時有時還能節省內存等等,好處很多。好的就要用就是這個哲學哈。此外map filter 得到的是iterator. 這一點設計的哲學如何理解呢?
另外generator 其實是generator iterators. 返回的是一個iterators . 但自己卻是一個function.
首先需要搞明白在 Python 中 iterator 和 generator 是不同的。
iterator 的定義是The essence of the Iterator Factory method Pattern is to "Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
在 Python 中 iterator 是這樣定義的。
An object representing a stream of data. Repeated calls to the iterator』s next() method return successive items in the stream. When no more data are available a StopIteration exception is raised instead.
而 generator 的定義是
A function which returns an iterator. It looks like a normal function except that it contains yield statements for producing a series a values usable in a for-loop or that can be retrieved one at a time with the next() function.
iterator 是一種常見的設計模式在 Python 中恰好可以用 generator 這種 function 實現而已。iterator 有其他的實現方式,例如自己在類里定義__iter__ 和 next 方法。generator 也有其它很多的功能,例如模擬 Coroutines(PEP 342 -- Coroutines via Enhanced Generators)。
iterator 卻是一個object
Python 中所有的東西都是 object。
參考資料
Python Types and ObjectsPEP 255 -- Simple Generators5. Built-in Types5. Built-in TypesGlossary — Python 2.7.8 documentationDesign Patterns暫時還沒有用到python3,如樓上所說,在python2中map和filter返回的是list,如果python3返回iterator的話,那可能是一些設計上的改進。其實map/filter在python2.5中用的比較多,一般稱這類用法為「以數據為中心」的編程,或者「函數式」編程。此後,List Comprehension/Generator Expression成了更為推薦的寫法,據說也是借鑒於函數式編程語言Haskell,但寫法上相比於map/filter更加Pythonic。
OK,言歸正傳,鑒於對python3不是很了解,我就僅僅針對python2中的實現談談自己的理解吧。
樓上有答主已經提到:iterator是一種設計模式,在python中恰好可以用generator function來實現
撇開「模式」這樣的詞語,用更加Pythonic的話來說就是:
在python中iterator可以視為一種協議(protocol),或者規則(regulation)
我會在下文解釋什麼是「協議」,且耐心聽我娓娓道來。
1. 碼字千萬,栗子先行
既然要討論迭代器,那就先來看看迭代器在python中的一個重要的用途:迭代我們知道list, tuple, dict都是可迭代對象(iterable object),換句話說,我們可以通過for...in...語句塊,遍歷其中的每一個元素。比如,遍歷一個list中的元素,將它們的值列印出來:&>&>&> alist = [1, 2, 3, 4, 5, 6, 7, 8]
&>&>&> for elem in alist:
... print elem,
...
1 2 3 4 5 6 7 8
&>&>&>
&>&>&> atup = (1, 2, 3, 4, 5, 6, 7, 8)
&>&>&> for elem in atup:
... print elem,
...
1 2 3 4 5 6 7 8
&>&>&>
甚至迭代一個字典,寫法也十分類似:
&>&>&> adict = {"name":"Jhon", "age":22, "job":"stu"}
&>&>&> for elem in adict:
... print elem,
...
job age name
&>&>&>
慢著,好像有哪裡不對。迭代字典的時候,只遍歷了其中的key,而沒有迭代value,這是為什麼呢?
原因是,for...in...其實是python的一顆「語法糖」。實際上,解釋器首先調用了內建的 iter() 方法,將in後的對象轉換成了迭代器,然後逐次調用迭代器的next()方法(python3中是__next__()方法), 從而遍歷每一個元素:iter(...)
iter(collection) -&> iterator
iter(callable, sentinel) -&> iterator
&>&>&> iter(alist)
&
&
&>&>&> iter(adict)
&
&>&>&>
看到 iter(adict) 的返回結果是 dictionary-keyiterator,可見返回的是一個「鍵迭代器」,因此for...in...只迭代了字典的鍵,而沒有迭代值。
有經驗的python程序員會使用字典的 iteritems() 方法來迭代「鍵-值對」:&>&>&> for key, value in adict.iteritems():
... print key, value
...
job stu
age 22
name Jhon
再來看看 iteritems() 方法返回了什麼:
&>&>&> adict.iteritems()
&
&>&>&>
如我們所料,返回了一個 dictionary-itemiterator,即「鍵-值對迭代器」,自然既可以迭代鍵,也可以迭代值。
2. 「鴨子「類型 協議不知題主是否注意到,iterator似乎有很多種?前面已經看到的,包括listiterator/tupleiterator/dictionary-keyiterator/dictionary-itemiterator,它們有一個共同的行為(上文我在解釋for...in...語法糖的時候已經提到了):實際上,一個標準的迭代器實現大概是下面這個樣子的:可以逐次調用迭代器的next()方法(python3中是__next__()方法), 從而遍歷每一個元素
class IteratorName(object):
def __init__(self, *args, **kwargs):
pass
def __iter__(self):
return self
def next(self):
pass
- __init__()方法:用於傳入一些初始化參數,以及設置一開始的計數器位置;
- __iter__()方法:往往都是返回自身;
- next()方法:最為重要的一個方法,用於返回下一個迭代的元素(事實上每次調用會返回當前計數器所在的位置,同時計算下一個位置);
題主如果有自己實現過一些python的內置數據結構(比如list),那對這種形式的迭代器應該不陌生,舉很久以前我寫過的一個栗子(實現的是一個Array,可以看作是大小固定的list):
class _ArrayIterator(object):
def __init__(self, theArray):
self._arrayRef = theArray
self._curNdx = 0 #一開始的計數器位置
def __iter__(self):
return self
def next(self):
if self._curNdx &< len(self._arrayRef):
item = self._arrayRef[self._curNdx]
self._curNdx += 1
return item
else:
raise StopIteration
只需要在Array的實現中,定義__iter__()方法,並且返回一個_ArrayIterator的實例,Array的實例就成為了一個可迭代對象:
class Array(object):
def __init__(self, size):
assert size &> 0, "Array size must be &> 0"
self._size = size
PyArrayType = ctypes.py_object * size
self._elements = PyArrayType()
self.clear(None)
[...] #other code
def __iter__(self):
return _ArrayIterator(self._elements) #返回一個_ArrayIterator的實例
class _BSTIter(object):
def __init__(self, curr):
self._curr = curr
self._queue = []
def __iter__(self):
return self
def next(self):
if self._curr == None:
raise StopIteration
_result = self._curr._nodeValue
if self._curr._nodeLeft != None:
self._queue.append(self._curr._nodeLeft)
if self._curr._nodeRight != None:
self._queue.append(self._curr._nodeRight)
if len(self._queue) &> 0:
self._curr = self._queue.pop(0)
else:
self._curr = None
return _result
尤其是next()方法,總是返回當前位置的元素,並且計算下一個迭代位置,直到沒有可迭代元素時拋出StopIteration異常
是時候引出「鴨子」類型(duck typing)的概念了,這個概念來自於一句諺語:
如果看起來像鴨子、叫起來像鴨子、走起路來也像鴨子,那它就是鴨子
看起來是一句很傻的話,卻是python很重要的設計哲學,它涉及到一些動態綁定的知識,同時又有很多python程序員充分利用這一哲學實現更好的解耦,這裡就不展開討論了。
看到這兒,不知題主是否產生了一些想法?其實我已經在很多地方給出了暗示哦,甚至將一些關鍵的詞語進行了加粗顯示,比如本節一開始那段:共同的行為,以及向上幾行:行為又是一致的這樣的辭彙。
如果題主還是沒有想到什麼,那讓我們套用剛才的那句諺語,給迭代器下個定義:如果看起來像迭代器、叫起來像迭代器、走起路來也像迭代器,那它就是迭代器
換言之:
不過問題是,怎麼能具備相同的行為特徵呢?這就是靠「協議」來實現的。如果一個對象具備迭代器所具有的所有行為特徵,那它就是迭代器了
協議:其實就是一些介面規範,換句話說,就是一些實現了指定的功能的方法。當你在某個類中實現了這樣的一種方法,那麼該類的實例就是滿足這種協議的。
還是拿next()方法舉例,它需要滿足一些特定的條件:返回當前值、計算下一位置、適當的時候拋出StopIteration異常。滿足了這些功能,具體你怎麼實現它就是你自己的事兒啦。
反過來思考一下,當你把實現這些功能的next()方法應用到某個對象時,就使得這個對象具備了迭代器的一些行為特徵。實際上你就是在構造一個迭代器了。
3. 迭代器 v.s. 生成器
如果題主看到這裡,對迭代器還是雲里霧裡的感覺,而對「鴨子」類型、協議這樣的詞語更是摸不著頭腦。那不妨類比一下數據結構中ADT(Abstract Data Type,抽象數據類型)的概念。
在實現某種數據結構之前,我們往往會先給出這種數據結構的完整的抽象定義,包括它所要包含的方法,以及這寫方法應該執行的操作。比如一個Stack的ADT定義可能是這樣的://Pseudocode
ADT Stack{
[...] //other code
push(x); //入棧
pop(); //出棧
peek(); //獲取棧頂元素
size(); //獲取棧中元素的數量
isempty(); //判空
isfull(); //判滿
[...] //other code
}
ADT給出了棧這種數據結構所應該遵守的規則(包含哪些方法及方法該做的事情),遵照這種規則實現的對象就被稱為棧。思考下,迭代器不也是這樣么?
迭代器並非某種具體的內置類型實例化得到的對象。
為了說明這一點,讓我們來看一下types模塊,這個模塊中給出了所有python解釋器認可的內嵌類型:
&>&>&> import types, pprint
&>&>&> pprint.pprint(dir(types))
["BooleanType",
"BufferType",
"BuiltinFunctionType",
"BuiltinMethodType",
"ClassType",
"CodeType",
"ComplexType",
"DictProxyType",
"DictType",
"DictionaryType",
"EllipsisType",
"FileType",
"FloatType",
"FrameType",
"FunctionType",
"GeneratorType", #生成器
"GetSetDescriptorType",
"InstanceType",
"IntType",
"LambdaType",
"ListType",
"LongType",
"MemberDescriptorType",
"MethodType",
"ModuleType",
"NoneType",
"NotImplementedType",
"ObjectType",
"SliceType",
"StringType",
"StringTypes",
"TracebackType",
"TupleType",
"TypeType",
"UnboundMethodType",
"UnicodeType",
"XRangeType",
"__builtins__",
"__doc__",
"__file__",
"__name__",
"__package__"]
&>&>&>
我們並沒有找到任何單獨標識迭代器的類型。
但你一定注意到了GeneratorType這一行,沒錯,不同於迭代器,生成器是一種實實在在的內嵌類型:&>&>&> import types
&>&>&>
&>&>&> def gen(N): #gen只是生成器函數
... for i in range(N):
... yield i,
...
&>&>&> g = gen(10) #注意這一行,g才是生成器對象
&>&>&> type(g)
&
&>&>&>
&>&>&> type(g) == types.GeneratorType #不是某種協議或規則,而是一種實際的類型
True
&>&>&>
When you call a generator function, it doesn』t return a single value; instead it returns a generator object that supports the iterator protocol
這裡面有三個地方需要注意,我用粗體+下劃線標識出來了。分別進行說明:
- 生成器函數就是生成器函數,和其他函數一樣,它是一個函數對象,類型是FunctionType。不要和生成器對象混淆;
- 生成器函數返回的是生成器對象,這是一個具體的類型實例化得到的,對應GeneratorType;
- 生成器對象支持iterator protocol也就是上文反覆提到的迭代器協議,它支持迭代器的行為,根據duck typing的原則,你可以將生成器認為是迭代器,並且完全當作是迭代器使用;
&>&>&> pprint.pprint(dir(g))
["__class__",
"__delattr__",
"__doc__",
"__format__",
"__getattribute__",
"__hash__",
"__init__", #[1]
"__iter__", #[2]
"__name__",
"__new__",
"__reduce__",
"__reduce_ex__",
"__repr__",
"__setattr__",
"__sizeof__",
"__str__",
"__subclasshook__",
"close",
"gi_code",
"gi_frame",
"gi_running",
"next", #[3]
"send",
"throw"]
&>&>&>
跟隨我的標號[1]、[2]、[3],看到那熟悉的三個方法了么?
顯然生成器和迭代器並不是同一個東西,它們有一些概念重疊的地方,但生成器還有很多迭代器不具備的能力,例如對coroutine的部分支持,如果樓主有接觸過Tornado對這點的體會就會很深厚了。
此外,關於生成器,官方文檔裡面還有另外一段描述,值得一提:Any function containing a yield keyword is a generator function; this is detected by Python』s bytecode compiler which compiles the function specially as a result
生成器是由python的位元組碼編譯器識別並進行特殊編譯的。這種處理方式也是有別於一般的迭代器的。
這意味著生成器對象雖然包含了我們在迭代器中見到的三個方法,但這是有位元組碼編譯器處理得到的,並沒有人為去定義,我們在實現時只是使用了yield關鍵字。因此,不能將它們等價於迭代器中的對應方法。前面有關iterator protocol證據的描述,只是為了更形象的解釋。實際使用時請區別對待。
4. 一切皆對象
題主對iterator是object表示了疑惑,說明題主並沒有理解」python中一切皆對象「的道理。雖然前面我似乎反覆在說iterator是某種概念性的東西、是協議定義的一種抽象類型或者如上一位答主說的是一種模式。但是具體到任何一種具體的實現,標準定義的類也好、或者通過生成器函數來實現,得到的實例對象和生成器對象都是對象。
不僅如此,函數也是對象、模塊也是對象,"hello,world"、[1, 2, 3]、(1, 2, 3)、{"name":"Jhon"}...,都是對象。甚至類本身也是對象,這就涉及到元類的知識了,題主可以自己去查找相關資料。
所以,毋庸置疑,任何一個具體的iterator實例都是一個object。
5. 迭代器 v.s 可迭代對象例如你定義了一個list:&>&>&> alist = [1, 2, 3, 4, 5]
&>&>&>
它雖然可以支持迭代,比如應用於for...in...語句塊。但是它卻不是一個迭代器,而是一個可迭代對象(iterable object)。
list類型的實例也包含一個__iter__()方法,不過不是返回的自身,而是返回了一個可以用於迭代的迭代器實例:&>&>&> alist.__iter__()
&
是不是很熟悉呢?沒錯,內建的iter()就是調用了對象的__iter__()方法,返回的迭代器對象。
如果題主不是很明白我所描述的__iter__()方法,可以退回到前面,研究一下Array那個例子。
6. 題外話(duck typing的補充):類文件對象當我們用open()函數打開一個文件,就得到了一個文件對象:&>&>&> f = open("/home/chiyu/auto/auto.py", "r")
&>&>&>
接下來,我們就可以調用相應的方法,進行讀(read)、寫(write)、調整文件指針(seek)、獲取文件指針位置(tell)等一系列應用於文件的操作,最後我們調用close()方法,將文件關閉。
而這一系列方法一樣可以應用於StringIO對象:&>&>&> import StringIO
&>&>&> s = StringIO.StringIO()
&>&>&> s.write("hello,world")
&>&>&> s.tell()
11
&>&>&> s.seek(-11, 2)
&>&>&> s.read()
"hello,world"
&>&>&> s.close()
&>&>&>
完全可以像操作文件一樣操作一個StringIO對象,因此,我們稱StringIO對象為」類文件「對象。這是duck typing的一個典型例子。
7. 總結- python中一切皆對象。沒錯,就是你所見到的一切;
- iterator是一個抽象定義:
準確的說:滿足iterator protocol的對象就可以被認為是一個iterator;
簡單粗暴的解釋:就是包涵了__init__(),__iter__(),next()三個方法,而且實現了這三個方法對應的指定的功能的對象;
- generator是一種具體的類型,它支持iterator protocol,因此可以被認為是迭代器,並且可以被當作迭代器使用,這完全得益於python的duck typing哲學。但是它的功能更強大,不能被簡單的認為就是iterator;
首先,Python 2里map和filter得到的是list。Python 3改成iterator是主要是為了省內存吧。
第二,只有一部分generator是iterator。以Python 3為例,generator的介面是send, throw這兩個函數。iterator的介面是 __next__。generator里的__next__相當於是 send(None)
&>&>&> def a():
... x = yield 1
... print(x)
...
&>&>&> g = a()
&>&>&> g.send(None)
1
&>&>&> g.send(2)
2
Traceback (most recent call last):
File "&
StopIteration
&>&>&> g = a()
&>&>&> g.__next__()
1
&>&>&> g.__next__()
None
Traceback (most recent call last):
File "&
StopIteration
&>&>&>
謝邀.
從來沒考慮過這個問題,我也不用 Python3(我就不用我就不用我就不用)從沒區分過 iterator 和 generator. (我估計我說的 generator 就是你說的 generator iterator,你說的 generator 我都是當成 function 的,只不過返回的是一個 generator(就是你說的 generator iterator))我只知道,列表解析方括弧改成圓括弧舊成了生成器了. 其實 map filter 返回的是不是 generator iterator 都沒有關係,想用的時候有 itertools 啊,裡邊返回的都是 generator iterator.PS:我覺得 bhuztez 說的挺好的- -,看來我有必要好好區分一下 iterator 和 generator.推薦閱讀:
※python楊輝三角代碼過程看不懂?
※這段python代碼的意思如何理解?
※如何使用爬蟲監控一系列網站的更新情況?
※python已正確安裝numpy但無法調用?
※我數學很差,最近報了培訓班在學Python編程,上課都能聽懂,自己寫程序的時候感覺特別難是怎麼回事?