重新排序类对象属性的最pythonic方法是什么?

时间:2017-06-16 00:13:24

标签: python xml dictionary attributes

我正在创建一组Python 2.7 / 3 +工具,以编程方式修改我的团队不断使用的XML文件的内容。到目前为止,我正在将XML解析为有意义的类对象属性(通过**kwargs.__dict__.update()setattr()),我将其与一些相当复杂的方法相关联。我非常希望能够根据方法调用来定义自动化,例如: foo.do_complex_modification(x)

我现在正处于将修改后的数据写回XML文件的位置,供我们的其他软件使用。该文件的内容很好,但遗憾的是我们的遗留工具只接受保留订单的XML ,这是我不能指望提供的Python对象字典。我可以在解决XML时没有问题地以“正确”的顺序阅读并以某种方式存储,但是修改遗留系统不是一种选择。

可能相关,还有XML的XSD架构。

问题:序列化我的类属性的最pythonic或优雅方式是什么,以便保留原始顺序?如果合适的话,当我从对象的.sort(key=ordering_function)读回来时,我会怎样写作__dict__

class Foo(object):
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)
    def really_complex_method(self):
        pass


def attrs(cls):   
    return [(k, v) for k, v in cls.__dict__.items() if k[:1] != '_']

d = dict(bar=1, baz=2, quux=3)  # Need them back in this particular order
print(attrs(Foo(**d)))

返回

[('quux', 3), ('bar', 1), ('baz', 2)]

在Python 3.6之前的任何版本上都没有订购。

2 个答案:

答案 0 :(得分:3)

最大的问题可能是因为你不断传递关键字参数,这不会保留它们所定义的顺序。另一个问题是class.__dict__是无序的。这是一种解决这两件事的方法(不假设您希望按名称按字母顺序排列属性)。替换类'special __dict__属性的技术是允许的,因为正如documentation所说,它可以是“字典或其他映射对象”(强调我的)。在Python 2和3中都是如此。

from collections import OrderedDict
from operator import itemgetter

class Foo(object):
    def __init__(self, *keyvalues):
        self.__dict__ = OrderedDict(keyvalues)

    def really_complex_method(self):
        pass

def attrs(instance):
    """ Return a list of instance attributes sorted by their value. """
    return sorted(instance.__dict__.items(), key=itemgetter(1))

print(attrs(Foo(('bar', 1), ('baz', 2), ('quux', 3), ('question', -42))))

输出:

[('question', -42), ('bar', 1), ('baz', 2), ('quux', 3)]

使用OrderedDict的替代方法是创建自己的映射类。这是来自examplePEP 3115 takien(关于Python 3中的元类,但主题不相关)。它也适用于Python 2和3):

from operator import itemgetter

class MemberTable(dict):
    """ Custom dictionary that keeps track of the order members (keys) are added. """
    def __init__(self, *args, **kwargs):
        super(MemberTable, self).__init__(*args, **kwargs)
        self.member_names = []

    def __setitem__(self, key, value):
        # if the key is not already defined, add to the list of keys.
        if key not in self:
            self.member_names.append(key)
        super(MemberTable, self).__setitem__(self, key, value)

class Foo(object):
    def __init__(self, *keyvalues):
        self.__dict__ = MemberTable(keyvalues)

    def really_complex_method(self):
        pass

def attrs(instance):
    """ Return a list of instance attributes sorted their value. """
    return sorted(instance.__dict__.items(), key=itemgetter(1))

print(attrs(Foo(('bar', 1), ('baz', 2), ('quux', 3), ('question', -42))))

另一种方法,它不涉及更改实例的__dict__,而是让类跟踪添加到它的顺序属性,然后按顺序迭代它们:

from operator import itemgetter

class Foo(object):
    def __init__(self, *keyvalues):
        self._sequence = []
        for (key, value) in keyvalues:
            setattr(self, key, value)
            self._sequence.append(key)  # keep track of order added

    def __iter__(self):
        for key in self._sequence:
            yield key, getattr(self, key)

    def really_complex_method(self):
        pass

def attrs(instance):
    """ Return a list of instance attributes sorted their value. """
    return sorted((item for item in instance), key=itemgetter(1))

print(attrs(Foo(('bar', 1), ('baz', 2), ('quux', 3), ('question', -42))))

注意在所有这些实现中,如果未使用sorted() attrs()函数,则会按添加顺序访问属性(这是在修改你的问题之前,你原本似乎只想要的东西。)

答案 1 :(得分:0)

即使您对类属性进行排序,只要它们存储在dict中,订单就会发生变化。最好的方法是使用OrderedDict来保留sorted()方法的顺序。

from collections import OrderedDict

class Foo(object):
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)
    def really_complex_method(self):
        pass


def attrs(cls):
    return OrderedDict(sorted(cls.__dict__.items()))

d = dict(bar=1, baz=2, quux=3)
print(attrs(Foo(**d)))