为什么有些对象可以用for … in 循环
我们先看一段代码:
list = [1, 2, 3, 4, 5]
for i in list:
logger.info(i)
这代码定义了1个数组object list, 然后用 for … in 来遍历这个list
看起来合理没什么值得注意
但其实 for … in 后面对象还可以是个String
for i in "hello":
logger.info(i)
还可以是个dict (相当于java 的map)
dict = {"a": 1, "b": 2}
for key in dict:
logger.info("{}:{}".format(key, dict[key]))
甚至可以是1个文件 open 对象 (TextIOWrapper)
with open("/home/gateman/Documents/jumpserver_installation.log") as f:
for i in f:
logger.info(i)
这样就不是那么能理解了,
for i in loopable_object 这种写法, 用java 的思维来推导 这个loopable_object 一定是实现了某个接口 类似与Loopable
实际上, 这个想法不是全错, 虽然python 没有强接口概念, 但是实际上, python 这个Loopable_object 必须具有 __iter__方法
也就是讲 它必须是1个iterable 对象
iterable 和 iterator 的定义
iterable :
Iterable 的中文意思是可迭代对象, 就是可以被循环的对象, 它在内部必须实现__iter__ 方法
而 iter 方法会返回1个 iterator , 所以实际上iterable 是依赖于它内置的 iterator去迭代元素的
常见的iterable 有List, Dict, String 等等, 所以它们都是可以用for … in … 来循环的
iterator:
上面说了, iterator 中文是迭代器, 它才是真正可以被迭代的对象, 它必须实现__next__ 方法
可以理解为 Iterator 有1个属性 current-item
next 方法会return 当前的current-item
而且会把 current -item 指向 下个item (置于如何找到下个item case by case, 看具体实现)
所以下次调用__next__ 就会返回上一次调用的next item了
而且 iterator 对象也可以用 python built-in 函数next() 来调用
例如
def test_iterator():
list = [1, 2, 3]
iter = list.__iter__()
while True:
try:
print(next(iter))
except StopIteration:
break
上面代码我们多次调用next(iter) 也能实现遍历, 当尝试去获取最后1个元素的next() 对象时,会产生StopIteration Exception
简单归纳下:
真正 可以遍历的东西是 iterator 迭代器, 它必须实现__next__ 接口用于 返回当前的item并改变状态令当前item指向下个
而 iterable 可迭代对象 里面必须实现__iter__ 方法来内置1个iterator
这样iterable 可以被 for … in 来遍历
实际上, 大部分iterator对象, 除了实现__next__方法外, 还实现__Iter__ 方法但让其return 自身
def __iter__(self):
return self
这样 这个iterator 本身也是个iterable 。
而且官方貌似鼓励这么做, 但是个人不是很喜欢。
写1个自定义 iterator 和iterable 例子, python 中的链表
python 不像java 没有内置链表 link list 这个容器.
但是我们可以利用迭代器自己写1个, 当然只实现链表部分简单的功能。
实现这个链表需要3个类
- Node - 这个是1个具有链结构(尾部指针)的数据存储对象
- LinkListIterator - link list 的迭代器, 链表的遍历的核心(根据尾部指针来找到下1个元素)
- LinkList - Iterable , 它的__iter__ 会返回上面的iterator, 但是它还包括链表的一些操作, 例如构建链表, append 等
Node 类
很简单定义1个value 属性和 next 属性就行
class Node:
def __init__(self, value):
self._value = value
self._next = None
@property
def value(self):
return self._value
@property
def next(self):
return self._next
@next.setter
def next(self, next):
self._next = next
@value.setter
def value(self, value):
self._value = value
测试代码:
def test_node():
logger.info("test_node")
node = Node(1)
assert node.value == 1
node.value = 2
assert node.value == 2
def test_node_next():
logger.info("test_node_next")
node1 = Node(1)
node2 = Node(2)
node1.next = node2
assert node1.next == node2
assert node1.next.value == 2
置于这里的value 可以传入任意类的对象, 天然泛型了这是
LinkListIterator 类定义
上提到了, LinkListIterator
必须有1个current_node 对象保存当前的Node 是什么
而的__next__ 方法要做到两件事情
- return 当前node 的值(注意是Node 的属性value 而不是Node 对象本身)
- current_node 要指向下1个item
如果当前node 已经是找不到or None, 则raise StopIteration
为了也能用for … in 来循环它, 我还是让它也实现__iter__ return 其本身
所以这里LinkListIterator 实际是也是iterable(it 上而不是业务逻辑上)
class LinkListIterator:
def __init__(self, _first_node) -> None:
self._current_node = _first_node
def __iter__(self):
return self
def __next__(self):
if not self._current_node:
raise StopIteration
current = self._current_node
self._current_node = current.next
return current.value
LinkListIterator 类测试
我们写1个元素类作为测试
staff.py
from loguru import logger
class Staff:
def __init__(self, id, name):
self._id = id
self._name = name
def __repr__(self):
return "Staff({}, {})".format(self._id, self._name)
就两个属性id 和 name
测试代码:
def sample2():
bill = Staff(2, "bill")
jack = Staff(1, "jack")
mike = Staff(3, "mike")
bill_node = Node(bill)
bill_node.next = Node(jack)
bill_node.next.next = Node(mike)
linkListIterator = LinkListIterator(bill_node)
logger.info(linkListIterator._current_node.value) # bill
# next() is a built-in function that will call the __next__() method of the iterator
# in this case , it will return node's value but not node itself
logger.info(next(linkListIterator)) # jack
logger.info(next(linkListIterator)) # mike
logger.info(linkListIterator._current_node.value) # mike
# if we want use for loop, we need to implement __iter__() method in LinkListIterator
# otherwise we will get TypeError: 'LinkListIterator' object is not iterable
for staff in linkListIterator:
logger.info(staff)
其实看出, linkListIterator 可以作为1个链表容器, 遍历对象, 但是并不优雅, 它向使用者暴露了Node 这个中间数据仓库类.
LinkList 类定义
为了真正的实现链表功能, 我们还需要1个容器类LinkList, 而它必须是1个iterator, 它只要让 iter 指向LinkListIterator就好
from loguru import logger
from src.iterator.sample_link_list.link_list_iterator import LinkListIterator
from src.iterator.sample_link_list.node import Node
class LinkList:
def __init__(self, first) -> None:
node = Node(first)
_first_node = node
_last_node = node
def __init__(self, *values) -> None:
if len(values) < 1:
raise ValueError("At least one node is required")
self._first_node = Node(values[0])
current = self._first_node
for i in range(1, len(values)):
current.next = Node(values[i])
current = current.next
self._last_node = current
def __iter__(self):
return LinkListIterator(self._first_node)
# to print all nodes's value but not nodes themselves
def print_nodes(self):
current = self._first_node
while current:
logger.info(current.value)
current = current.next
def get_length(self):
current = self._first_node
count = 0
while current:
count += 1
current = current.next
return count
def append(self, value):
if self.get_length() == 0:
self._first_node = Node(value)
self._last_node = self._first_node
else:
self._last_node.next = Node(value)
self._last_node = self._last_node.next
这个LinkList 实现了两种构造方法, 接受单个元素和一组元素作为参数
而且它在内部调用Node 对象, 向用户隐藏了这个细节
它有两个关键内部属性, 头指针 和 尾部指针
其实理论上有头指针就可以, 但是留有尾部指针可以大大 减少 append 元素方法的内部查询次数
我在这个类 只实现了 get_length() append() print_nodes() (其实可以用for loop 代替) 方法
有必要的话 删除元素, 中间插入元素, 检查元素是否存在的方法还是可以再加上的
LinkList 类测试
测试代码:
def sample3():
bill = Staff(2, "bill")
jack = Staff(1, "jack")
mike = Staff(3, "mike")
link_list = LinkList(jack)
link_list.print_nodes()
link_list = LinkList(bill, jack, mike);
link_list.print_nodes()
logger.info("length of link_list: {}".format(link_list.get_length())) # 3
link_list.append(Staff(4, "Ted"))
link_list.append(Staff(5, "Peter"))
logger.info("length of link_list: {}".format(link_list.get_length())) # 5
for i in link_list:
logger.info(i)
可以见到, 使用iterable 比直接用iterator 优雅得多了, 符合人类正常的思维。