在Python中序列化一个suds对象

20

好的,我正在努力提高Python技能水平,所以我不确定我现在做的是否正确,但这是我的当前问题...

我需要通过SOAP方法获取一些信息,现在只使用部分信息,但将整个结果存储以供将来使用(我们需要尽可能少地使用该服务)。查找访问该服务的最佳方法时,我发现suds是可行的方式,它很简单并且可以轻松获取数据。但现在我想以某种方式保存结果,最好是序列化/存储到数据库中,以便稍后检索并使用。

这样做的最佳方法是什么,看起来pickle / json不是一个选项?谢谢!

更新 阅读如何pickle suds结果? 的顶级答案让我更好地理解为什么这不是一个选项,我猜我必须重新创建一个具有所需信息的基本对象?

7个回答

35

我一直在使用以下方法将Suds对象转换为JSON:

from suds.sudsobject import asdict

def recursive_asdict(d):
    """Convert Suds object into serializable format."""
    out = {}
    for k, v in asdict(d).items():
        if hasattr(v, '__keylist__'):
            out[k] = recursive_asdict(v)
        elif isinstance(v, list):
            out[k] = []
            for item in v:
                if hasattr(item, '__keylist__'):
                    out[k].append(recursive_asdict(item))
                else:
                    out[k].append(item)
        else:
            out[k] = v
    return out

def suds_to_json(data):
    return json.dumps(recursive_asdict(data))

请注意,该函数不适用于 sud 对象列表。可以使用 [recursive_asdict(d) for d in ds] 解决此问题。 - Jon
2
@Jon,它能够工作是因为有 elif isinstance(v, list) 的检查。 - Rafay
使用这个解决方案时,我遇到了错误:AttributeError: 'dict' object has no attribute 'iteritems' - Guilherme Matheus
1
@GuilhermeMatheus 嘿嘿,这段代码是针对Python2的。我希望现在已经修复了 :) - plaes
@Rafay 我能够使用参数 retxml=True 将 XML 响应中的数据解析为 JSON 对象。但非常感谢您更快速的编辑回复! - Guilherme Matheus
十年后仍然运行良好。感谢您的努力。 - Liquidgenius

7

是的,我确认我在你提到的答案中所给出的解释--动态生成的类不容易被pickle(或其他方式容易序列化),您需要提取所有状态信息,pickle 状态,并在检索时重构棘手的sudsobject,如果您真的坚持使用它;-)。


5
我正在使用'retxml=True'选项来仅获取原始结果,然后将其转换为字典(应该是可序列化的)- 看起来会起作用...谢谢! - jeffff
2
@pssdbt,非常好 - 或者您可以序列化XML本身,但我相信解封装一个已经封装的字典会比解析XML更快,所以我认为您做出了正确的选择。 - Alex Martelli

6

在我进行研究并找到这个答案之前,我想分享一下我的想法。这对于我来说在处理复杂的suds响应和其他对象(如__builtins__)时非常有效,因为解决方案是与suds无关的:

import datetime

def object_to_dict(obj):
    if isinstance(obj, (str, unicode, bool, int, long, float, datetime.datetime, datetime.date, datetime.time)):
        return obj
    data_dict = {}
    try:
        all_keys = obj.__dict__.keys()  # vars(obj).keys()
    except AttributeError:
        return obj
    fields = [k for k in all_keys if not k.startswith('_')]
    for field in fields:
        val = getattr(obj, field)
        if isinstance(val, (list, tuple)):
            data_dict[field] = []
            for item in val:
                data_dict[field].append(object_to_dict(item))
        else:
            data_dict[field] = object_to_dict(val)
    return data_dict

这个解决方案可行且实际上速度更快。它也适用于没有__keylist__属性的对象。

我在一个复杂的suds输出对象上进行了100次基准测试,这个解决方案的运行时间为0.04到0.052秒(平均值为0.045724287)。而上面的recursive_asdict解决方案的运行时间为0.082到0.102秒,几乎是两倍(平均值为0.0829765582)。

然后我回到起点重新编写了函数,以使其性能更好,并且它不需要导入datetime。我利用了__keylist__属性,因此这对于其他对象(如__builtins__)不起作用,但对于suds对象输出效果很好:

def fastest_object_to_dict(obj):
    if not hasattr(obj, '__keylist__'):
        return obj
    data = {}
    fields = obj.__keylist__
    for field in fields:
        val = getattr(obj, field)
        if isinstance(val, list):  # tuple not used
            data[field] = []
            for item in val:
                data[field].append(fastest_object_to_dict(item))
        else:
            data[field] = fastest_object_to_dict(val)
    return data

运行时间为0.18 - 0.033秒(平均0.0260889721秒),比 recursive_asdict 解决方案快近4倍。


5

我实现了一个虚拟类的对象实例,用于Suds,并能够序列化。FakeSudsInstance 的行为像原始的 Suds 对象实例,如下所示:

from suds.sudsobject import Object as SudsObject

class FakeSudsNode(SudsObject):

    def __init__(self, data):
        SudsObject.__init__(self)
        self.__keylist__ = data.keys()
        for key, value in data.items():
            if isinstance(value, dict):
                setattr(self, key, FakeSudsNode(value))
            elif isinstance(value, list):
                l = []
                for v in value:
                    if isinstance(v, list) or isinstance(v, dict):
                        l.append(FakeSudsNode(v))
                    else:
                        l.append(v)
                setattr(self, key, l)
            else:
                setattr(self, key, value)


class FakeSudsInstance(SudsObject):

    def __init__(self, data):
        SudsObject.__init__(self)
        self.__keylist__ = data.keys()
        for key, value in data.items():
            if isinstance(value, dict):
                setattr(self, key, FakeSudsNode(value))
            else:
                setattr(self, key, value)

    @classmethod
    def build_instance(cls, instance):
        suds_data = {}
        def node_to_dict(node, node_data):
            if hasattr(node, '__keylist__'):
                keys = node.__keylist__
                for key in keys:
                    if isinstance(node[key], list):
                        lkey = key.replace('[]', '')
                        node_data[lkey] = node_to_dict(node[key], [])
                    elif hasattr(node[key], '__keylist__'):
                        node_data[key] = node_to_dict(node[key], {})
                    else:
                        if isinstance(node_data, list):
                            node_data.append(node[key])
                        else:
                            node_data[key] = node[key]
                return node_data
            else:
                if isinstance(node, list):
                    for lnode in node:
                        node_data.append(node_to_dict(lnode, {}))
                    return node_data
                else:
                    return node
        node_to_dict(instance, suds_data)
        return cls(suds_data)

现在,例如以下的suds调用之后:
# Now, after a suds call, for example below
>>> import cPickle as pickle
>>> suds_intance = client.service.SomeCall(account, param)
>>> fake_suds = FakeSudsInstance.build_instance(suds_intance)
>>> dumped = pickle.dumps(fake_suds)
>>> loaded = pickle.loads(dumped)

希望这能有所帮助。


node_data.append(node_to_dict(lnode, {})) 是错误的,因为它将一个字典传递给了 node_to_dict 函数。 - Mike Milkin
if hasattr(node, '__keylist__'): 语句块中的 return node_data 应该也返回 FakeSudsNode(node_data),因为它组成了一个字典。 - Mike Milkin

3
上述建议的解决方案会丢失有关类名称的重要信息 - 这对于某些库(如 DFP client https://github.com/googleads/googleads-python-lib)可能具有价值,其中实体类型可能被编码在动态生成的类名称中(例如 TemplateCreative/ImageCreative)。
以下是我使用的解决方案,它保留类名并恢复字典序列化对象而不会丢失数据(除了 suds.sax.text.Text 被转换为常规 unicode 对象和其他一些类型我还没有遇到的情况)。
from suds.sudsobject import asdict, Factory as SudsFactory

def suds2dict(d):                                                               
    """                                                                         
    Suds object serializer 
    Borrowed from https://dev59.com/c3E95IYBdhLWcg3wMrEB#15678861                                                     
    """                                                                         
    out = {'__class__': d.__class__.__name__}                                   
    for k, v in asdict(d).iteritems():                                          
        if hasattr(v, '__keylist__'):                                           
            out[k] = suds2dict(v)                                               
        elif isinstance(v, list):                                               
            out[k] = []                                                         
            for item in v:                                                      
                if hasattr(item, '__keylist__'):                                
                    out[k].append(suds2dict(item))                              
                else:                                                           
                    out[k].append(item)                                         
        else:                                                                   
            out[k] = v                                                          
    return out                                                                  


def dict2suds(d):                                                               
    """                                                                         
    Suds object deserializer                                                    
    """                                                                         
    out = {}                                                                    
    for k, v in d.iteritems():                                                  
        if isinstance(v, dict):                                                 
            out[k] = dict2suds(v)                                               
        elif isinstance(v, list):                                               
            out[k] = []                                                         
            for item in v:                                                      
                if isinstance(item, dict):                                      
                    out[k].append(dict2suds(item))                              
                else:                                                           
                    out[k].append(item)                                         
        else:                                                                   
            out[k] = v                                                          
    return SudsFactory.object(out.pop('__class__'), out)  

只是想指出,对于Python 3,请使用items()替换iteritems()。感谢您提供的精彩答案! - Matthew
我只需要 dict2suds 部分,但在 out.pop('__class__') 部分出现了错误,但将其删除并直接替换为 .object(dict=out) 可以解决我的问题。感谢您的提示! - Ícaro

2
我更新了上面的 recursive_asdict 示例以上链接,使其兼容python3(使用items代替iteritems)。
from suds.sudsobject import asdict
from suds.sax.text import Text

def recursive_asdict(d):
    """
    Recursively convert Suds object into dict.
    We convert the keys to lowercase, and convert sax.Text
    instances to Unicode.

    Taken from:
    https://dev59.com/c3E95IYBdhLWcg3wMrEB#15678861

    Let's create a suds object from scratch with some lists and stuff
    >>> from suds.sudsobject import Object as SudsObject
    >>> sudsobject = SudsObject()
    >>> sudsobject.Title = "My title"
    >>> sudsobject.JustAList = [1, 2, 3]
    >>> sudsobject.Child = SudsObject()
    >>> sudsobject.Child.Title = "Child title"
    >>> sudsobject.Child.AnotherList = ["4", "5", "6"]
    >>> childobject = SudsObject()
    >>> childobject.Title = "Another child title"
    >>> sudsobject.Child.SudObjectList = [childobject]

    Now see if this works:
    >>> result = recursive_asdict(sudsobject)
    >>> result['title']
    'My title'
    >>> result['child']['anotherlist']
    ['4', '5', '6']
   """
    out = {}
    for k, v in asdict(d).items():
        k = k.lower()
        if hasattr(v, '__keylist__'):
            out[k] = recursive_asdict(v)
        elif isinstance(v, list):
            out[k] = []
            for item in v:
                if hasattr(item, '__keylist__'):
                    out[k].append(recursive_asdict(item))
                else:
                    out[k].append(
                        item.title() if isinstance(item, Text) else item)
        else:
            out[k] = v.title() if isinstance(v, Text) else v
    return out

0

我喜欢这种方式。我们不需要自己进行迭代,而是在将其转换为字符串时由Python进行迭代。

class Ob:
    def __init__(self, J) -> None:
        self.J = J
    
    def __str__(self):           

        if hasattr(self.J, "__keylist__"):
            self.J = {key: Ob(value) for key, value in dict(self.J).items()}
        if hasattr(self.J, "append"):
            self.J = [Ob(data) for data in sefl.J]
        return str(self.J)

result = Ob(result_soap)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接