是否使用在某处实现的decorator在python中自定义YAML序列化?

时间:2017-12-16 19:56:42

标签: python serialization deserialization yaml

我正在python中编写YAML配置序列化(使用YAML,因为它是一个对象树配置,我希望配置尽可能具有人工可读性。)

我有几个问题:

  1. 对象使用的几个内部(非配置)成员,因此我希望不存储在配置文件中
  2. 某些配置成员具有默认值,如果它们是默认值,我不想存储它们(这也不会触及反序列化)
  3. 在Java中你有.is() here s.a. @JsonInclude(Include.NON_NULL)以及为json文件执行此操作的其他人,我在python中发现yaml(甚至对于JSON)没有类似的东西,我知道如何编写它(使用YAML包API)但我不想如果它已经是在某个地方实施。

    我要序列化的类的示例

     class Locator(object):
         def __init__(self, multiple=False):
             # configurable parameters
             self.multiple = multiple
    
             # internal parameters (used in locate method)
             self.precedents = []
             self.segments
             self.probabilities = []
            def locate(self, message):
             """
              do stuff to locate stuff on message
             """ . . .
                 yield segment
    

    这里我们看到保存配置参数(多个)的根类,如果它是True,我只希望序列化,以及在其操作s.a中使用的其他成员。儿子(先例)等...我根本不想序列化

    任何人都可以帮我吗?

2 个答案:

答案 0 :(得分:2)

我认为诚实的答案可能不是",原因是你在这里所达到的并不是真正的Python惯用语。如果你稍微眯一下,那么Python dicts和JSON对象之间有很强的相似性 - 而且斜视更多,YAML看起来像JSON的空白方言 - 所以当我们需要从Python序列化thing时我们倾向于将一些thing的自定义映射写入dict,将其填充到JSON / YAML序列化器中,并完成它。

thing => dict步骤中,有一些快捷方式和惯用法可以派上用场。例如,当您在其上调用asdict时,带有方法的namedTuple子类将使所述方法退出:

In [1]: from collections import namedtuple

In [2]: class Locator(namedtuple("Locator", "foo bar baz")):
   ...:     def hide(self):
   ...:         pass
   ...:

In [3]: wow = Locator(1,2,3)

In [4]: wow._asdict()
Out[4]: OrderedDict([('foo', 1), ('bar', 2), ('baz', 3)])

当然元组是不可变的,所以如果你真的需要一个具有可变属性的类,这不是一个通用的解决方案,而且这并没有解决你在声明中从序列化中删除某些属性的愿望。方式。

可能符合您需求的一个不错的第三方库是attrs ...这个库提供了类似于一个具有很多可定制性的特殊命名元组,包括过滤器和默认值,您可以使用它们在你觉得舒服的东西。它不是1:1,而是你在这里所达到的目标,但它可能是一个开始。

答案 1 :(得分:0)

这是我同时为JSON写的一个解决方案,但是它非常具体,我很想找到一个已经解决这个问题的软件包更加通用

class LocatorEncoder(json.JSONEncoder):
    """
        custom Locator json encoder to encode only configuration related parameters
        it encodes only :
            1. protected data members (whose names start with a single '_'
            2. members that are different from their default value

        NOTE: for this filtering encoder to work two strong conventions must be upheld, namely:
            1. Configuration data members must start with a single preceding '_'
            2. They must differ from their correlated __init__ parameter by only that '_'
    """
    @staticmethod
    def get_default_args(f):
        return {
            k: v.default
            for k, v in inspect.signature(f).parameters.items()
            if v.default is not inspect.Parameter.empty
        }

    @staticmethod
    def filter(d, defaults):
        """
            this is the filtering method
        :param d: dictionary of members to filter
        :param defaults: default values to filter out
        :return: ordered dictionary with only protected members ('_') that do not have their default value
            the return dictionary is ordered because it prints nicer
        """
        filtered = OrderedDict()
        for k, v in d.items():
            # this is the filter logic (key starts with one _ and is not its default value)
            if (re.match(r'_[^_]', k)) and (k[1:] not in defaults or defaults[k[1:]] != v):
                filtered[k] = v
        return filtered

    def default(self, o):
        """
            iterate on classes in the objects mro and build a list of default constructor values

        :param o: the object to be json encoded
        :return: encoded dictionary for the object serialization
        """
        if isinstance(o, Locator):
            defaults = {}
            for cl in o.__class__.mro():
                # iterate on all the default arguments of the __init__ method
                for k, v in self.get_default_args(cl.__init__).items():
                    # update the key with value if it doesn't already exist
                    defaults[k] = defaults.get(k, v)

            # build the filtered configuration data members and add precedent in a recursive call to this default method
            filtered_dictionary = self.filter(o.__dict__, defaults)
            precedents = []
            for precedent in o.precedents:
                precedents.append(self.default(precedent))
            filtered_dictionary["precedents"] = precedents

            return {'__{}__'.format(o.__class__.__name__): filtered_dictionary}
        return super().default(self, o)