Python pickle在__getattr__中返回默认值时崩溃

6 投票
2 回答
2750 浏览
提问于 2025-04-16 19:56

我有一个像字典一样的类,用来存储一些值作为属性。最近我添加了一些逻辑(__getattr__),让它在属性不存在时返回 None。可是一这样做,pickle 就崩溃了,我想知道这是为什么?

测试代码:

import cPickle
class DictionaryLike(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def __iter__(self):
        return iter(self.__dict__)

    def __getitem__(self, key):
        if(self.__dict__.has_key(key)):
            return self.__dict__[key]
        else:
            return None

    ''' This is the culprit...'''    
    def __getattr__(self, key):
        print 'Retreiving Value ' , key
        return self.__getitem__(key)

class SomeClass(object):
    def __init__(self, kwargs={}):
       self.args = DictionaryLike(**kwargs)


someClass = SomeClass()
content = cPickle.dumps(someClass,-1)
print content

结果:

Retreiving Value  __getnewargs__
Traceback (most recent call last):
  File <<file>> line 29, in <module>
    content = cPickle.dumps(someClass,-1)
TypeError: 'NoneType' object is not callable`

我是不是做了什么傻事?我看到有帖子说,deepcopy() 可能需要我在键不存在时抛出一个异常?如果是这样,有没有简单的方法可以实现我想要的,而不需要抛出异常呢?

最终结果是,如果有些调用

someClass.args.i_dont_exist

我希望它返回 None。

2 个回答

5

当你的类里没有某个属性时,你需要抛出一个 AttributeError 错误:

def __getattr__(self, key):
    i = self.__getitem__(key)
    if i == None:
        raise AttributeError
    return self.__getitem__(key)

我假设这是必须的行为。根据 Python 的文档,getattr 的说明,“当在常规地方找不到某个属性时会被调用(也就是说,它既不是实例属性,也不在类的继承树中)。name 是属性的名称。这个方法应该返回计算出的属性值,或者抛出一个 AttributeError 异常。”

如果你不抛出这个异常,就没有办法告诉 pickle 等工具它要找的属性不存在。例如,在你的错误信息中,pickle 正在寻找一个叫 __getnewargs__ 的特殊可调用方法,pickle 期望如果没有找到 AttributeError 异常,那么返回值是可以调用的。

我想一个可能的解决办法是,你可以尝试把 pickle 要找的所有特殊方法定义为虚拟方法?

13

实现 __getattr__ 有点复杂,因为它会在每次访问不存在的属性时被调用。在你的情况下,pickle 模块会检查你的类是否有一个特殊的方法 __getnewargs__,但是它得到了 None,显然这个东西是不能被调用的。

你可能想要修改 __getattr__,让它在处理一些特殊名称时调用基类的实现:

def __getattr__(self, key):
    if key.startswith('__') and key.endswith('__'):
        return super(DictionaryLike, self).__getattr__(key)
    return self.__getitem__(key)

我通常会跳过所有以下划线开头的名称,这样我就可以避免处理内部符号的特殊情况。

撰写回答