Search code examples
pythonyamlpyyaml

pyyaml load number as decimal


yaml.load loads numbers as Python floats. I cannot find a straightforward way to override this.

Compare json.load, which allows parse_float=Decimal if you want to parse floating point numbers as decimal.Decimals.

Is there any way to accomplish this with PyYAML? Or is this inadvisable by some property of the YAML spec?


Solution

  • You can do something like:

    def decimal_constructor(loader, node):
        value = loader.construct_scalar(node)
        return Decimal(value)
    
    yaml.add_constructor(u'!decimal', decimal_constructor)
    

    This allows you to load decimals, but only if they are prefixed with the !decimal tag in the YAML document. However, you can use a custom Resolver to resolve all numbers to !decimal:

    class MyResolver(BaseResolver):
        pass
    
    MyResolver.add_implicit_resolver(
            '!decimal',
            re.compile(r'''^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)?
                        |\.[0-9_]+(?:[eE][-+][0-9]+)?
                        |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*
                        |[-+]?\.(?:inf|Inf|INF)
                        |\.(?:nan|NaN|NAN))$''', re.X),
            list('-+0123456789.'))
    

    You should copy the other implicit resolvers over from the default resolver. Then, you need to define a Loader that uses your resolver.

    class MyLoader(Reader, Scanner, Parser, Composer, SafeConstructor, MyResolver):
        def __init__(self, stream):
            Reader.__init__(self, stream)
            Scanner.__init__(self)
            Parser.__init__(self)
            Composer.__init__(self)
            SafeConstructor.__init__(self)
            MyResolver.__init__(self)
    

    Above, we added your constructor to the default loader. Change that line to:

    yaml.add_constructor(u'!decimal', decimal_constructor, MyLoader)
    

    Finally, load the YAML using

    yaml.load(stream, MyLoader)