包装一个Python对象

1 投票
3 回答
1310 浏览
提问于 2025-04-15 11:42

我想把Python对象转换成plist格式,反过来也可以做到(这可以用plistlib来实现)。我的想法是写一个叫PlistObject的类,它可以包装其他对象:

def __init__(self, anObject):
     self.theObject = anObject

并且提供一个“写入”方法:

def write(self, pathOrFile):
    plistlib.writeToPlist(self.theObject.__dict__, pathOrFile)

现在,如果PlistObject的行为和它所包装的对象完全一样,那就太好了,也就是说,所有的属性和方法都能“转发”到被包装的对象上。我意识到可以用__getattr____setattr__来处理复杂的属性操作:

    def __getattr__(self, name):
         return self.theObject.__getattr__(name)

但这样的话,我就会遇到一个问题:构造函数会导致无限递归,因为self.theObject = anObject也试图访问被包装的对象。

我该如何避免这个问题呢?如果这个想法听起来不太好,也请告诉我。

3 个回答

1

我很高兴看到其他人能帮你解决关于__getattr__的递归调用问题。既然你想了解将数据序列化为plist的整体方法,我也想分享一些我的想法。

Python的plist实现只处理基本类型,并没有提供扩展机制来让你指示它如何序列化或反序列化复杂类型。比如说,如果你定义了一个自定义类,writePlist就无法帮忙,因为你已经发现你传递的是实例的__dict__来进行序列化。

这有几个影响:

  1. 你不能用这个方法来序列化包含其他非基本类型对象的对象,除非把它们转换成__dict__,而且要递归地处理整个网络图。

  2. 如果你自己写一个网络图遍历器来序列化所有可以访问的非基本对象,你还得担心图中可能出现的循环,比如一个对象的属性中有另一个对象,而那个对象又指向第一个对象,等等。

因此,你可能想考虑使用pickle,因为它可以处理这些问题,甚至更多。如果你因为其他原因需要plist格式,并且你确定可以只使用“简单”的对象字典,那么你可以考虑使用一个简单的函数……试图让PlistObject模拟被包含对象的每一个可能功能就像剥洋葱一样,可能会有很多层,因为你需要处理被包装实例的所有可能性。

像这样简单的做法可能更符合Python的风格,而且通过不进行包装,可以让被包装对象的可用性更简单:

def to_plist(obj, f_handle):
    writePlist(obj.__dict__, f_handle)

我知道这看起来不太吸引人,但在我看来,这比包装器要更易于维护,因为plist格式的限制很大,而且肯定比强迫你应用中的所有对象都继承一个公共基类要好,尤其是当你的业务领域中没有任何东西表明这些不同的对象是相关的时候。

3

如果我没有理解错的话,这样做是完全没问题的:

def __getattr__(self, name):
    return getattr(self.theObject, name)

补充:对于那些认为查找 self.theObject 会导致无限递归调用 __getattr__ 的人,我来给你解释一下:

>>> class Test:
...     a = "a"
...     def __init__(self):
...         self.b = "b"
...     def __getattr__(self, name):
...         return 'Custom: %s' % name
... 
>>> Test.a
'a'
>>> Test().a
'a'
>>> Test().b
'b'
>>> Test().c
'Custom: c'

__getattr__ 只有在最后的情况下才会被调用,也就是当你找不到你想要的属性时才会用到。因为 theObject 可以在 __dict__ 中找到,所以不会出现问题。

2

但是我遇到了一个问题,就是构造函数现在会导致无限循环,因为self.theObject = anObject这行代码也试图访问被包装的对象。

所以手册建议你在访问所有“真实”的属性时都这样做。

theobj = object.__getattribute__(self, "theObject")

撰写回答