[关闭]
@Frankchen 2016-03-24T12:48:21.000000Z 字数 2877 阅读 1529

理解Python中的for指令

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语句所做的事情。当你写下:

  1. for name in train:
  2. do something with name

解释器将仅仅取得train[0]并且把它分派给name,然后执行代码块。之后解释器将会取得train[1]train[2]等,直到它获得一个IndexError
for-in循环中的代码和周围的代码被执行的范围是一样的,例子如下:

  1. train = 1, 2, 3
  2. for name in train:
  3. value = name * 10
  4. print value

变量 trainnamevalue将处于同一个命名空间内。
当然,这种方式看起来貌似很直接,但是,如果你意识到你可以使用客观对象当做trains时就很有趣了。只要执行__getitem__方法,你就可以控制循环如何发生。下面代码:

  1. class MyTrain:
  2. def __getitem__(self, index):
  3. if not condition:
  4. raise IndexError("that's enough!")
  5. value = fetch item identified by index
  6. return value # hand control back to the block
  7. for name in MyTrain():
  8. do something with name

只要条件语句为真,循环将使用custom trains的变量一直执行下去。另一方面来讲,do something部分变为了一块在自定义序列对象控制下被执行的代码。如上的代码和之下的等效:

  1. index = 0
  2. while True: # run forever
  3. if not condition:
  4. break
  5. name = fetch item identified by index
  6. do something with name
  7. index = index + 1

除了index是一个隐藏变量,并且控制代码被放置在分开的对象中。
你可以在许多方面使用这种机制,如产生句子成分:

  1. class MySequence:
  2. def __getitem__(self, index):
  3. if index > 10:
  4. raise IndexError("that's enough!")
  5. return value * 10 # returns 0, 10, 20, ..., 100

也可以用来从外部源取得数据:

  1. class MyTable:
  2. def __getitem__(self, index):
  3. value = fetch item index from database table
  4. if value not found:
  5. raise IndexError("not found")
  6. return value

或者从stream取得数据:

  1. class MyFileIterator:
  2. def __getitem__(self, index):
  3. text = get next line from file
  4. if end of file:
  5. raise IndexError("end of file")
  6. return text

亦或者从别的源取得数据:

  1. class MyFileIterator:
  2. def __getitem__(self, index):
  3. text = get next line from file
  4. if end of file:
  5. raise IndexError("end of file")
  6. return text

之后的例子会更加详细,但是在所有的例子中,在__getitem__之中的代码本质上都是把for-in循环内的代码块当做句内的callback。
并且我们也注意到最后两个例子不关注索引而是只是调用for-in块直到跑完了数据。或者,更加不明显一点的来讲,直到它们跑完了内部索引变量的比特。

为了对付这个问题,并且避免有些对象看起来很像序列但是却不支持随机访问的问题,for-in指令在Python2.2中被重新设计了。for-in现在寻找__iter__而不是__getitem__接口。如果现在,这种方法被调用,并且结果对象之后被用来一个接一个的获得元素。这种新的协议像这样:

  1. obj = train.__iter__()
  2. name = obj.next()
  3. do something with name
  4. name = obj.next()
  5. do something with name
  6. ...

这之中obj是内部变量,并且next方法以升起StopIterator例外而不是IndexError指示数据的末端。使用自定义对象可以看成类似于:

  1. class MyTrain:
  2. def __iter__(self):
  3. return self
  4. def next(self):
  5. if not condition:
  6. raise StopIteration
  7. value = calculate next value
  8. return value # hand control over to the block
  9. for name in MyTrain():
  10. do something with name

(这里,MyTrain对象返回自身,这意味着for-in指令将持续调用MyTrainnext方法去执行。在某种程度上,它使得循环可以使用独立对象)。
使用这种机制,我们现在可以把之前的文件迭代器重写为:

  1. class MyFileIterator:
  2. def __iter__(self):
  3. return self # use myself
  4. def next():
  5. text = get next line from file
  6. if end of file:
  7. raise StopIteration()
  8. return text

并且,只添加一点额外的工作,我们得到了一个不支持普通的索引的对象并且在一个超过两百万行的文件上不会崩溃。

但是,原始的sequence是什么?这当然是被包装对象很容易解决的,并且保持一个内部的计数器,然后映射next引用到__getitem__的引用,实际上和for-in是一样的。对于这样的一个对象,iter,Python提供了一个标准的完成,而当__iter__不存在时iter自动被使用。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注