You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义任意顺序树遍历遇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):
---&gt; 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):
---&gt; 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):
---&gt; 47     for x in self.traverse(child):
     48         print(str(x.folder))
     49 else:

TypeError: 'NoneType' object is not iterable

问题原因与修复方案

核心问题

  1. 遍历方法无返回值:Tree.traverse方法没有返回可迭代对象,但代码中尝试用for x in self.traverse(child)遍历其返回值。当遍历到叶子节点时,traverse执行完print后默认返回None,循环遍历None直接触发TypeError。
  2. 重复打印逻辑:遍历子节点时,每次循环都打印所有子节点路径(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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 08:27:36