0

Is there an easy way to convert string representations such as "50%" to a float in python? I have data in yaml and am parsing it. I would like to support "0.5" and "50%" as valid representations. So, stripping "%" alone is not enough.

Phil Lord
  • 2,917
  • 1
  • 20
  • 31
  • 1
    Possible duplicate of [What is a clean way to convert a string percent to a float?](https://stackoverflow.com/questions/12432663/what-is-a-clean-way-to-convert-a-string-percent-to-a-float) – Red Cricket Dec 02 '18 at 20:18
  • Hey guys, the question is about "0.5" *and* "50%" ! – quant Dec 02 '18 at 20:21
  • ´print ([float(string.split("%")[0])/100 if len(string.split("%"))>1 else float(string) for string in strings])´ – Liam Dec 02 '18 at 20:22

3 Answers3

4

What about:

def parseFloat(str):
    try:
        return float(str)
    except:
        str = str.strip()
        if str.endswith("%"):
            return float(str.strip("%").strip()) / 100
        raise Exception("Don't know how to parse %s" % str)

print(parseFloat("50%"))
print(parseFloat("0.5"))

The code even supports parsing a string that looks like so:

print(parseFloat("  50  % "))
quant
  • 2,184
  • 2
  • 19
  • 29
0

You could convert the string using this:

a = "40.3%"
b = float(a[:-1])/100
p0kero
  • 120
  • 1
  • 1
  • 12
0

Any number sequence in YAML that ends in a percentage sign, will normally be loaded as a string scalar, because the % will not make the scalar match any other pattern (in particular not that of integers or floats). You can of course walk recursively over the data structure loaded from YAML and patch things, but if there are tags in the datastructure, for constructing specific objects, recursing into those is at least non-trivial, if not impossible to do generically.

The best solution would be that your YAML would explicitly state what is a percentage via a tag. E.g.:

a: !Percentage 60%

, where the object loaded from that tag behaves like a the float 0.6. But tags are underused when working with YAML and you don't indicate your input is tagged in such a way.

Fortunately it is not too difficult to setup a parser for percentages in a loader, in the following I will do so for the default RoundTripLoader.

The YAML document in.yaml, to be parsed:

%YAML 1.2
---
a: 60.3%
42%: 'abc'

You can hook into the representer for strings to check if they consist of numbers and end in a percentage sign, but it is faster to add a resolver:

import sys
import re
from pathlib import Path
import ruamel.yaml
from ruamel.yaml.util import RegExp 


ruamel.yaml.resolver.VersionedResolver.add_implicit_resolver(
    u'percentage',
    RegExp(u'''^(?:[-+]?[0-9_]+%
        |[-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?%
        )$''', re.X
    ),
    list(u'-+0123456789.'))


def construct_percentage(self, node):
     value = float(node.value[:-1]) / 100.0
     return value


ruamel.yaml.constructor.RoundTripConstructor.add_constructor(
    u'percentage', construct_percentage
)


yaml = ruamel.yaml.YAML()
data = yaml.load(Path('in.yaml'))
print(dict(data))

which gives:

{'a': 0.603, 0.42: 'abc'}

Please note:

  • by default the data is loaded in a subclass of ordereddict, hence the call to dict().
  • if you also want to dump the percentages, you should load them as a subclass of float, so they are distinguisable from normal floats when you are going to dump them.
  • I intentionally left out the additional formats of ints (hex, octal, etc.) and floats (scientific notation, NaN, etc). They don't make much sense IMO, but they could be re-allowed via the patterns matcher.
Anthon
  • 69,918
  • 32
  • 186
  • 246
  • This is a nice answer, but I am using pyyaml. I don't really want to go the route of making the YAML more explicit because it needs to remain easy to type! – Phil Lord Dec 04 '18 at 22:32
  • What would not be easy to type? Are you referring to the "%YAML" tag an and directive indicator? I hope you realize those are optional. – Anthon Dec 05 '18 at 06:10
  • I meant the tags (`!Percentage`) – Phil Lord Dec 05 '18 at 21:21
  • Then use the alternative in the second part of the answer and register your pattern, that doesn't require extra typing during YAML editing. – Anthon Dec 06 '18 at 07:12