首先,我已经看到了很多与此相关的问题(将字符串转换为浮点数等),但我需要一些更通用的东西,但我找不到(所以我希望这也能帮助其他人类似的问题)。我已经提出了一个解决方案,但我想知道它是否是 1)性能和 2)pythonic 优雅方面的最佳解决方案。
简而言之,问题是:
- 我从各种来源获取数据,这些数据被制成带有字典的列表(作为行/列表设置)。
- 多样性意味着我不能依赖固定的输入类型(基本上它们可能是字符串、布尔值、整数、浮点数),但用户可以指定哪些列(字典中的键)是值。
- 然后我需要将其转换为实际值类型(我们在这里谈论的是数百万行数据,因此性能相当关键)。
- 如果输入不是实数(例如:'aaa'),那么它应该返回 None。
- 可能有货币符号和千位分隔符(需要删除)和小数分隔符(如果不是点,则需要用标准点替换)
那么我做了什么:
import ast
import types
NumberTypes = (types.IntType, types.LongType, types.FloatType, types.ComplexType)
def mk_value(s, currency_sign='', thousand_sep='', decimal_sep='.'):
if isinstance(s, bool): # make boolean into a 0/1 value
if s:
result = 1
else:
result = 0
elif isinstance(s, NumberTypes): # keep numbers as/is
result = s
else: # convert a string
# prepare the string for conversion
if currency_sign != '':
s = s.replace(currency_sign, '')
if thousand_sep != '':
s = s.replace(thousand_sep, '')
if decimal_sep != '.':
s = s.replace(decimal_sep, '.')
s = s.strip()
# convert the string
if s == '':
result = None
else:
try:
# convert the string by a safe evaluation
result = ast.literal_eval(s)
# check if result of the evaluation is a number type
if not isinstance(result, NumberTypes):
result = None
except ValueError:
# if the conversion gave an error, the string is not a number
result = None
return result
您可以通过以下方式对其进行测试:
mk_value(True)
mk_value(1234)
mk_value(1234.56)
mk_value('1234')
mk_value('1234.56')
mk_value('1,234.56') # without an explicit decimal separator this is not a number
mk_value('1.234.567,89 EUR', currency_sign='EUR', thousand_sep='.', decimal_sep=',') # all exceptions
所以这有效(据我所知);但这是最好/最蟒蛇的方式吗?有更快的方法吗?我应该为此调查 Cython 吗?任何关于改进这一点的想法都会非常有帮助!
BR
卡斯特
编辑:我已经根据 Andrew 和 WoLpH 的建议更新了我的代码。现在看起来像这样:
import types
NumberTypes = (types.IntType, types.LongType, types.FloatType, types.ComplexType)
def mk_value(s, currency_sign='', thousand_sep='', decimal_sep='.'):
if isinstance(s, bool): # make boolean into a 0/1 value
if s:
result = 1
else:
result = 0
elif isinstance(s, NumberTypes): # keep numbers as/is
result = s
else: # convert a string
# prepare the string for conversion
if currency_sign:
s = s.replace(currency_sign, '')
if thousand_sep:
s = s.replace(thousand_sep, '')
if decimal_sep != '.':
s = s.replace(decimal_sep, '.')
s = s.strip()
# convert the string
if not s: # if the string is empty, it's not a number
result = None
else:
try: # try int
result = int(s)
except ValueError:
try: # if there's an error, try float
result = float(s)
except ValueError:
# if the conversion gave an error, the string is not a number
result = None
return result
之前的代码的表现是这样的:
>>> timeit.timeit("mk_value(1234)", 'from __main__ import mk_value', number=100000)
0.050575971603393555
>>> timeit.timeit("mk_value(1234.56)", 'from __main__ import mk_value', number=100000)
0.07073187828063965
>>> timeit.timeit("mk_value('1234')", 'from __main__ import mk_value', number=100000)
0.8333430290222168
>>> timeit.timeit("mk_value('1234.56')", 'from __main__ import mk_value', number=100000)
0.8230760097503662
>>> timeit.timeit("mk_value('1,234.56', thousand_sep=',')", 'from __main__ import mk_value', number=100000)
0.9358179569244385
新代码的性能:
>>> timeit.timeit("mk_value(1234)", 'from __main__ import mk_value', number=100000)
0.04723405838012695
>>> timeit.timeit("mk_value(1234.56)", 'from __main__ import mk_value', number=100000)
0.06952905654907227
>>> timeit.timeit("mk_value('1234')", 'from __main__ import mk_value', number=100000)
0.1798090934753418
>>> timeit.timeit("mk_value('1234.56')", 'from __main__ import mk_value', number=100000)
0.45616698265075684
>>> timeit.timeit("mk_value('1,234.56', thousand_sep=',')", 'from __main__ import mk_value', number=100000)
0.5290899276733398
所以这要快得多:最复杂的几乎快两倍,而 int 快得多(我猜它是 try/except 逻辑中的第一个)!真的很棒,谢谢你的意见。
我现在将其保持开放状态,看看是否有人对如何改进有一个绝妙的主意:) 至少我希望这会在未来对其他人有所帮助(这一定是一个非常普遍的问题)