Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 651
jsharp Avatar asked Nov 16 '17 22:11

jsharp


1 Answers

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)
like image 198
flyx Avatar answered Nov 10 '22 20:11

flyx