自定义任意顺序树遍历遇TypeError:NoneType不可迭代问题排查
问题
尝试根据文件元数据(日期、地点)和指定条件自动构建文件夹结构,例如测试条件COND = ["Y", "m", "C"]代表先按年份、再按月份、最后按国家拆分文件。已完成测试数据与过滤函数编写,Node类构建树的逻辑可正常拆分数据,但使用Tree类的traverse方法遍历树时,仅遍历部分子树就出现TypeError: 'NoneType' object is not iterable错误,无法完成全树遍历。
测试数据
import datetime as dt data = [ ["111", dt.datetime(2019, 1, 1), "Aus", "Bri"], ["112", dt.datetime(2019, 1, 5), "Aus", "Bri"], ["113", dt.datetime(2019, 2, 10), "Aus", "Mel"], ["114", dt.datetime(2020, 1, 1), "Aus", "Per"], ["115", dt.datetime(2020, 1, 10), "Aus", "Per"], ["116", dt.datetime(2020, 1, 25), "Aus", "Per"], ["117", dt.datetime(2020, 10, 5), "My", "KL"], ["118", dt.datetime(2020, 11, 6), "Ru", "Led"], ["119", dt.datetime(2020, 12, 1), "Ru", "Mos"], ["120", dt.datetime(2021, 3, 5), "Aus", "Syd"], ["121", dt.datetime(2021, 5, 1), "Aus", "Mel"], ["122", dt.datetime(2021, 6, 1), "Aus", "Per"], ["123", dt.datetime(2021, 11, 1), "Chi", "Bei"], ["124", dt.datetime(2021, 11, 15), "Jp", "Tok"], ["125", dt.datetime(2022, 1, 1), "Aus", "Per"], ["126", dt.datetime(2022, 3, 1), "Aus", "Bri"], ["127", dt.datetime(2022, 3, 5), "Aus", "Per"], ["128", dt.datetime(2022, 3, 11), "My", "KL"], ["129", dt.datetime(2022, 5, 1), "Aus", "Syd"], ["130", dt.datetime(2022, 8, 8), "Aus", "Bri"], ]
过滤函数及条件字典
def filter_year(data: list[list[str | dt.datetime]]) -> set[int]: return {i[1].year for i in data} def filter_month(data: list[list[str | dt.datetime]]) -> set[int]: return {i[1].month for i in data} def filter_day(data: list[list[str | dt.datetime]]) -> set[int]: return {i[1].day for i in data} def filter_country(data: list[list[str | dt.datetime]]) -> set[str]: return {i[2] for i in data} def filter_city(data: list[list[str | dt.datetime]]) -> set[str]: return {i[3] for i in data} condition_dict = { "Y": {'fun': filter_year, 'id': 1 }, "m": {'fun': filter_month,'id': 1 }, "d": {'fun': filter_day,'id': 1}, "C": {'fun': filter_country, 'id': 2}, "c": {'fun': filter_city, 'id': 3 } }
Node类定义
from typing import Any from pathlib import Path from dataclasses import dataclass, field @dataclass class Node: folder: Path metadata: list[list[Any]] = field(default_factory=list) conditions: list[str] = field(default_factory=list) @property def children(self) -> list['Node']: if len(self.conditions) == 0: return [] current_condition = self.conditions[0] fun = condition_dict[current_condition]['fun'] fnames = fun(self.metadata) children_data = {str(n): {} for n in fnames} for f in fnames: children_data[str(f)]['folder'] = self.folder / str(f) children_data[str(f)]['conditions'] = self.conditions[1:] if current_condition == 'Y': children_data[str(f)]['metadata'] = [i for i in self.metadata if i[1].year == f] elif current_condition == 'm': children_data[str(f)]['metadata'] = [i for i in self.metadata if i[1].month == f] elif current_condition == 'd': children_data[str(f)]['metadata'] = [i for i in self.metadata if i[1].day == f] elif current_condition == 'C': children_data[str(f)]['metadata'] = [i for i in self.metadata if i[2] == f] elif current_condition == 'c': children_data[str(f)]['metadata'] = [i for i in self.metadata if i[3] == f] return [Node(**i) for i in children_data.values()]
Tree遍历类定义
@dataclass class Tree: def traverse(self, root: Node): r = root.children if not r or len(root.conditions) == 0: print('The end of subtree:', root.folder) else: for child in r: print('\n'.join(str(i.folder) for i in r)) if isinstance(child, Node): for x in self.traverse(child): print(str(x.folder)) else: print(child)
测试代码及报错
COND = ["Y", "m", "C"] n = Node(folder=Path('/home'), metadata=data, conditions=COND) tree = Tree() tree.traverse(n)
报错信息
/home/2019 /home/2020 /home/2021 /home/2022 /home/2019/1 /home/2019/2 /home/2019/1/Aus The end of subtree: /home/2019/1/Aus --------------------------------------------------------------------------- TypeError Traceback (most recent call last) /home/pavel/python/photo_manager/temp/tree_test.ipynb Cell 4 in <cell line: 4>() 1 n = Node(folder=Path('/home'), metadata=data, conditions=COND) 3 tree = Tree() ----> 4 tree.traverse(n) /home/pavel/python/photo_manager/temp/tree_test.ipynb Cell 4 in Tree.traverse(self, root) 45 print('\n'.join(str(i.folder) for i in r)) 46 if isinstance(child, Node): ---> 47 for x in self.traverse(child): 48 print(str(x.folder)) 49 else: /home/pavel/python/photo_manager/temp/tree_test.ipynb Cell 4 in Tree.traverse(self, root) 45 print('\n'.join(str(i.folder) for i in r)) 46 if isinstance(child, Node): ---> 47 for x in self.traverse(child): 48 print(str(x.folder)) 49 else: /home/pavel/python/photo_manager/temp/tree_test.ipynb Cell 4 in Tree.traverse(self, root) 45 print('\n'.join(str(i.folder) for i in r)) 46 if isinstance(child, Node): ---> 47 for x in self.traverse(child): 48 print(str(x.folder)) 49 else: TypeError: 'NoneType' object is not iterable
问题原因与修复方案
核心问题
- 遍历方法无返回值:Tree.traverse方法没有返回可迭代对象,但代码中尝试用
for x in self.traverse(child)遍历其返回值。当遍历到叶子节点时,traverse执行完print后默认返回None,循环遍历None直接触发TypeError。 - 重复打印逻辑:遍历子节点时,每次循环都打印所有子节点路径(
print('\n'.join(str(i.folder) for i in r))),会导致大量重复输出。
修复后的Tree类
修改traverse方法为生成器,返回节点迭代器,同时移除重复打印逻辑:
@dataclass class Tree: def traverse(self, root: Node): # 先返回当前节点 yield root r = root.children if r: for child in r: # 递归遍历子节点并返回 yield from self.traverse(child)
正确的遍历打印方式
调用遍历方法时,自行处理节点输出:
COND = ["Y", "m", "C"] n = Node(folder=Path('/home'), metadata=data, conditions=COND) tree = Tree() for node in tree.traverse(n): if not node.children: print('The end of subtree:', node.folder) else: print(node.folder)
额外优化:避免重复计算子节点
原Node类的children属性每次访问都会重新计算子节点,可改为初始化时一次性构建:
@dataclass class Node: folder: Path metadata: list[list[Any]] = field(default_factory=list) conditions: list[str] = field(default_factory=list) children: list['Node'] = field(default_factory=list) def __post_init__(self): if self.conditions: current_condition = self.conditions[0] fun = condition_dict[current_condition]['fun'] fnames = fun(self.metadata) for f in fnames: # 筛选当前子节点的元数据 if current_condition == 'Y': child_metadata = [i for i in self.metadata if i[1].year == f] elif current_condition == 'm': child_metadata = [i for i in self.metadata if i[1].month == f] elif current_condition == 'd': child_metadata = [i for i in self.metadata if i[1].day == f] elif current_condition == 'C': child_metadata = [i for i in self.metadata if i[2] == f] elif current_condition == 'c': child_metadata = [i for i in self.metadata if i[3] == f] else: child_metadata = [] # 创建子节点 child_node = Node( folder=self.folder / str(f), metadata=child_metadata, conditions=self.conditions[1:] ) self.children.append(child_node)
内容的提问来源于stack exchange,提问作者NotAName
相关产品推荐
相关产品推荐

