7

I have a list of strings in the form like

a = ['str','5','','4.1']

I want to convert all numbers in the list to float, but leave the rest unchanged, like this

a = ['str',5,'',4.1]

I tried

map(float,a)

but apparently it gave me an error because some string cannot be converted to float. I also tried

a[:] = [float(x) for x in a if x.isdigit()]

but it only gives me

[5]

so the float number and all other strings are lost. What should I do to keep the string and number at the same time?

LWZ
  • 11,670
  • 22
  • 61
  • 79
  • Sorry I wasn't very clear, the list can be long and I do not know the exact order of the elements, meaning I do not know which one is number before hand. – LWZ Jan 31 '13 at 19:47
  • Did you try doing `'4.1'.isdigit()`, or reading [the docs](http://docs.python.org/2/library/stdtypes.html#str.isdigit)? "Return true if all characters in the string are digits…" Since `'.'` is not a digit, it returns false. – abarnert Jan 31 '13 at 20:43
  • @abarnert, you are right. I was wrong about 4.1 and it's now fixed. – LWZ Jan 31 '13 at 21:38

4 Answers4

7
>>> a = ['str','5','','4.1']
>>> a2 = []
>>> for s in a:
...     try:
...         a2.append(float(s))
...     except ValueError:
...         a2.append(s)
>>> a2
['str', 5.0, '', 4.0999999999999996]

If you're doing decimal math, you may want to look at the decimal module:

>>> import decimal
>>> for s in a:
...     try:
...         a2.append(decimal.Decimal(s))
...     except decimal.InvalidOperation:
...         a2.append(s)
>>> a2
['str', Decimal('5'), '', Decimal('4.1')]
bgporter
  • 35,114
  • 8
  • 59
  • 65
  • Thanks. But why do I get 4.0999999999999996? I would like to have same significant figure as the original number. – LWZ Jan 31 '13 at 20:01
  • 2
    @LWZ - thats the way floating point numbers work. See [this question](http://stackoverflow.com/questions/5997027/python-rounding-error-with-float-numbers). – Blair Jan 31 '13 at 20:37
  • 2
    @LWZ: Because you cannot represent `4.1` exactly as a `float`. There are roughly 69105.00000000000001 questions on SO about this, so search if you need more info. – abarnert Jan 31 '13 at 20:40
6
for i, x in enumerate(a):
    try:
        a[i] = float(x)
    except ValueError:
        pass

This assumes you want to change a in place, for creating a new list you can use the following:

new_a = []
for x in a:
    try:
        new_a.append(float(x))
    except ValueError:
        new_a.append(x)

This try/except approach is standard EAFP and will be more efficient and less error prone than checking to see if each string is a valid float.

Andrew Clark
  • 202,379
  • 35
  • 273
  • 306
  • 1
    The one-liner example will fail on `4.1` since the dot character is not a digit. Replacing `if i.isdigit()` with `if i.replace('.','').isdigit()` should get around that problem. – Valdogg21 Jan 31 '13 at 19:42
  • Yeah I just realized that, I just removed the one liner since you would also run into issues with negative numbers and scientific notation. Easier to just do the try/except. – Andrew Clark Jan 31 '13 at 19:43
5

Here's a way to do it without exception handling and using a bit of regex: -

>>> a = ['str','5','','4.1']
>>> import re
>>> [float(x) if re.match("[+-]?(?:\d+(?:\.\d+)?|\.\d+)$", x) else x for x in a]
4: ['str', 5.0, '', 4.1]

Note that, this regex will cover only a basic range of numbers, applicable in your case. For more elaborate regex to match a wider range of floating-point numbers, like, including exponents, you can take a look at this question: -

Community
  • 1
  • 1
Rohit Jain
  • 209,639
  • 45
  • 409
  • 525
  • Is it worth anchoring with ``$`` in the ``re`` ``"\d+(\.\d+)?$"``for the pathological cases of '3.14pi' etc. Best answer none the less. – sotapme Jan 31 '13 at 19:52
  • 1
    Your solution misses negative numbers, isn't it? All in all, it is almost never a good idea to use regexps unless they're really required. – Vladimir Jan 31 '13 at 19:54
  • @Vladimir.. Yeah, updated it. Didn't consider all cases on first go, based on the input by OP. – Rohit Jain Jan 31 '13 at 19:56
  • Also, `.5` and `1.` are still valid float point literals in python ;) – Vladimir Jan 31 '13 at 19:56
  • It seems, the right regexes have been presented at http://stackoverflow.com/questions/385558/python-and-regex-question-extract-float-double-value . – Vladimir Jan 31 '13 at 19:58
  • Also misses floats expressed in scientific notation like `3.1e2` – bgporter Jan 31 '13 at 20:50
  • @bgporter.. Yeah right. And that is why I've quoted this thing in my answer, with a link to another answer containing the actual regex. – Rohit Jain Jan 31 '13 at 20:52
4

My version:

def convert(value):
    try:
        return float(value)
    except ValueError:
        return value

map(convert, a)
sneawo
  • 3,543
  • 1
  • 26
  • 31