@Frankchen
2016-03-24T12:48:21.000000Z
字数 2877
阅读 1529
python
也许大家对于Python的其他的数据块指令不太熟悉,例如老旧的for-in
循环语句。表面上,Python的for-in
语句是从Python的前任ABC,当时它是这样被描述的:
FOR name,... IN train:
commands
Take each element of train in turn
在ABC中,在Python中被称为指令的叫做命令(commands),并且序列被称为trains。实际上,这门语言把其他语言中通用的各种称谓都改了名,也开发者认为这样更加有利于初学者掌握。
不管怎么说,为了取出在一个 train (sequence) 里的所有element (item),我们只要:
name = train[0]
do something with name
name = train[1]
do something with name
name = train[2]
do something with name
... etc ...
直到我们跑完了所有的元素。当我们到这一步时,我们得到一个IndexError
例外,它告诉我们是时候停下来了。
并且,在它的最简洁最初的形式里,这确实就是for-in
语句所做的事情。当你写下:
for name in train:
do something with name
解释器将仅仅取得train[0]
并且把它分派给name
,然后执行代码块。之后解释器将会取得train[1]
,train[2]
等,直到它获得一个IndexError
。
在for-in
循环中的代码和周围的代码被执行的范围是一样的,例子如下:
train = 1, 2, 3
for name in train:
value = name * 10
print value
变量 train
, name
和 value
将处于同一个命名空间内。
当然,这种方式看起来貌似很直接,但是,如果你意识到你可以使用客观对象当做trains
时就很有趣了。只要执行__getitem__
方法,你就可以控制循环如何发生。下面代码:
class MyTrain:
def __getitem__(self, index):
if not condition:
raise IndexError("that's enough!")
value = fetch item identified by index
return value # hand control back to the block
for name in MyTrain():
do something with name
只要条件语句为真,循环将使用custom trains的变量一直执行下去。另一方面来讲,do something部分变为了一块在自定义序列对象控制下被执行的代码。如上的代码和之下的等效:
index = 0
while True: # run forever
if not condition:
break
name = fetch item identified by index
do something with name
index = index + 1
除了index是一个隐藏变量,并且控制代码被放置在分开的对象中。
你可以在许多方面使用这种机制,如产生句子成分:
class MySequence:
def __getitem__(self, index):
if index > 10:
raise IndexError("that's enough!")
return value * 10 # returns 0, 10, 20, ..., 100
也可以用来从外部源取得数据:
class MyTable:
def __getitem__(self, index):
value = fetch item index from database table
if value not found:
raise IndexError("not found")
return value
或者从stream取得数据:
class MyFileIterator:
def __getitem__(self, index):
text = get next line from file
if end of file:
raise IndexError("end of file")
return text
亦或者从别的源取得数据:
class MyFileIterator:
def __getitem__(self, index):
text = get next line from file
if end of file:
raise IndexError("end of file")
return text
之后的例子会更加详细,但是在所有的例子中,在__getitem__
之中的代码本质上都是把for-in
循环内的代码块当做句内的callback。
并且我们也注意到最后两个例子不关注索引而是只是调用for-in
块直到跑完了数据。或者,更加不明显一点的来讲,直到它们跑完了内部索引变量的比特。
为了对付这个问题,并且避免有些对象看起来很像序列但是却不支持随机访问的问题,for-in
指令在Python2.2中被重新设计了。for-in
现在寻找__iter__
而不是__getitem__
接口。如果现在,这种方法被调用,并且结果对象之后被用来一个接一个的获得元素。这种新的协议像这样:
obj = train.__iter__()
name = obj.next()
do something with name
name = obj.next()
do something with name
...
这之中obj是内部变量,并且next
方法以升起StopIterator
例外而不是IndexError
指示数据的末端。使用自定义对象可以看成类似于:
class MyTrain:
def __iter__(self):
return self
def next(self):
if not condition:
raise StopIteration
value = calculate next value
return value # hand control over to the block
for name in MyTrain():
do something with name
(这里,MyTrain
对象返回自身,这意味着for-in
指令将持续调用MyTrain
的next
方法去执行。在某种程度上,它使得循环可以使用独立对象)。
使用这种机制,我们现在可以把之前的文件迭代器重写为:
class MyFileIterator:
def __iter__(self):
return self # use myself
def next():
text = get next line from file
if end of file:
raise StopIteration()
return text
并且,只添加一点额外的工作,我们得到了一个不支持普通的索引的对象并且在一个超过两百万行的文件上不会崩溃。
但是,原始的sequence是什么?这当然是被包装对象很容易解决的,并且保持一个内部的计数器,然后映射next
引用到__getitem__
的引用,实际上和for-in
是一样的。对于这样的一个对象,iter
,Python提供了一个标准的完成,而当__iter__
不存在时iter
自动被使用。