Python 2.7中使用AttrDict 2.0出现异常问题咨询
我之前也碰到过类似的坑!先把你的代码补全成可复现的完整版本,方便大家理解问题场景:
from attrdict import AttrDict class Test(object): pass try: for cls_ in [Test, AttrDict]: foo = cls_() print("\ntesting with class {}...".format(cls_.__name__)) # 核心逻辑:尝试给列表追加元素,不存在则默认初始化 foo.my_list += [1] print("Final value: {}, type: {}".format(foo.my_list, type(foo.my_list))) # 后续追加操作会触发异常 foo.my_list.append(2) except Exception as e: print("Error occurred: {}".format(e))
运行这段代码后,Test类会直接抛出AttributeError(因为my_list未定义),但AttrDict会先把my_list悄悄转成元组,后续执行append时才抛出'tuple' object has no attribute 'append'的异常。
问题根源
AttrDict 2.0在Python 2.7中的__getattr__方法有个特殊的“魔法”处理:当你访问一个不存在的属性(键)时,它不会像普通类那样抛出AttributeError,而是返回一个空元组。
这就导致foo.my_list += [1]的实际执行逻辑变成了:
foo.my_list = getattr(foo, 'my_list', ()) + [1]
空元组和列表相加的结果是元组(1,),这个元组被赋值给foo.my_list,后续的append操作自然会因为元组不可变而报错。
解决方案
针对这个问题,有几种稳妥的解决方式:
显式检查并初始化列表
不要依赖+=的隐式行为,先明确判断属性是否存在,不存在就初始化为空列表:for cls_ in [Test, AttrDict]: foo = cls_() print("\ntesting with class {}...".format(cls_.__name__)) if not hasattr(foo, 'my_list'): foo.my_list = [] foo.my_list.append(1) foo.my_list.append(2) print("Final value: {}, type: {}".format(foo.my_list, type(foo.my_list)))用
getattr显式指定列表默认值
如果喜欢简洁写法,可以通过getattr主动传入空列表作为默认值,避免AttrDict返回空元组:for cls_ in [Test, AttrDict]: foo = cls_() print("\ntesting with class {}...".format(cls_.__name__)) foo.my_list = getattr(foo, 'my_list', []) foo.my_list.append(1) foo.my_list.append(2) print("Final value: {}, type: {}".format(foo.my_list, type(foo.my_list)))升级AttrDict版本(若环境允许)
这个隐性bug在AttrDict 2.2.0及以后的版本中已经被修复,新版本访问不存在的属性会直接抛出AttributeError,和普通类行为一致。不过要注意确认新版本是否兼容Python 2.7。
额外提醒
在Python 2.7中使用AttrDict时,尽量避开它对不存在属性的隐式返回逻辑,这种“魔法”行为很容易引发难以排查的隐性问题,显式初始化是最稳妥的选择。
内容的提问来源于stack exchange,提问作者JL Peyret

