Конфигурация пользовательского локали для конвертации по плавающей позиции
Мне нужно преобразовать строку в формате "1.234.345,00"
в значение float
1234345.00
.
Один из способов - использовать повторное str.replace
:
x = "1.234.345,00"
res = float(x.replace('.', '').replace(',', '.'))
print(res, type(res))
1234345.0 <class 'float'>
Однако это выглядит ручным и не обобщенным. Это сильно upvoted ответ предлагает использовать locale
библиотеку. Но мой локаль по умолчанию не имеет тех же условностей, что и моя строка ввода. Затем я обнаружил способ извлечь символы, используемые в локальных соглашениях, в качестве словаря:
import locale
print(locale.localeconv())
{'int_curr_symbol': '', 'currency_symbol': '', 'mon_decimal_point': '',
..., 'decimal_point': '.', 'thousands_sep': '', 'grouping': []}
Есть ли способ обновить этот словарь, сохранить как пользовательский локаль, а затем иметь возможность вызвать этот пользовательский языковой стандарт. Что-то вроде:
mylocale = locale.create_new_locale() # "blank" conventions or copied from default
mylocale.localeconv()['thousands_sep'] = '.'
mylocale.localeconv()['decimal_point'] = ','
setlocale(LC_NUMERIC, mylocale)
atof('123.456,78') # 123456.78
Если это невозможно, как мы получим список всех доступных языковых стандартов и их условностей? Кажется, что анти-шаблон "выводит" правильную конфигурацию из соглашений (не говоря уже о неэффективности/ручной), поэтому я надеялся на универсальное решение, такое как выше псевдокод.
Edit: Здесь моя попытка найти все локали, где thousands_sep == '.'
и decimal_point == ','
. Фактически, в более общем плане, группировать локали по комбинациям этих параметров:
import locale
from collections import defaultdict
d = defaultdict(list)
for alias in locale.locale_alias:
locale.setlocale(locale.LC_ALL, alias)
env = locale.localeconv()
d[(env['thousands_sep'], env['decimal_point'])].append(alias)
Результат:
---------------------------------------------------------------------------
Error Traceback (most recent call last)
<ipython-input-164-f8f6a6db7637> in <module>()
5
6 for alias in locale.locale_alias:
----> 7 locale.setlocale(locale.LC_ALL, alias)
8 env = locale.localeconv()
9 d[(env['thousands_sep'], env['decimal_point'])].append(alias)
C:\Program Files\Anaconda3\lib\locale.py in setlocale(category, locale)
596 # convert to string
597 locale = normalize(_build_localename(locale))
--> 598 return _setlocale(category, locale)
599
600 def resetlocale(category=LC_ALL):
Error: unsupported locale setting
Ответы
Ответ 1
Если вы откроете исходный код для локали, вы увидите, что есть переменная с именем _override_localeconv
(которая, как представляется, предназначена для тестирования).
# With this dict, you can override some items of localeconv return value.
# This is useful for testing purposes.
_override_localeconv = {}
Попытка следующего действия, похоже, переопределяет словарь без изменения всей локали, хотя, вероятно, имеет некоторые непредвиденные последствия, тем более что изменение локалей не является потоковым. Быть осторожен!
import locale
locale._override_localeconv["thousands_sep"] = "."
locale._override_localeconv["decimal_point"] = ","
print locale.atof('123.456,78')
Попробуйте прямо сейчас!
Ответ 2
Здесь что-то, используя Вавилон, это работает для меня.
Сначала вы загружаете некоторые тестовые данные, с вашими ожиданиями, и он создает словарь разделителя для локального псевдонима, который подходит.
Затем вы можете конвертировать из этого пункта.
import string
from decimal import Decimal
from babel.numbers import parse_decimal, NumberFormatError
from babel.core import UnknownLocaleError
import locale
traindata = [
("1.234.345,00", Decimal("1234345.00")),
("1,234,345.00", Decimal("1234345.00")),
("345", Decimal("345.00")),
]
data = traindata + [
("345,00", Decimal("345.00")),
("345.00", Decimal("345.00")),
("746", Decimal("746.00")),
]
def findseps(input_):
#you need to have no separator
#or at least a decimal separator for this to work...
seps = [c for c in input_ if not c in string.digits]
if not seps:
return ""
sep = seps[-1]
#if the decimal is something then thousand will be the other...
seps = "." + sep if sep == "," else "," + sep
return seps
def setup(input_, exp, lookup):
key = findseps(input_)
if key in lookup:
return
for alias in locale.locale_alias:
#print(alias)
try:
got = parse_decimal(input_, locale=alias)
except (NumberFormatError,UnknownLocaleError, ValueError) as e:
continue
except (Exception,) as e:
raise
if exp == got:
lookup[key] = alias
return
def convert(input_, lookup):
seps = findseps(input_)
try:
locale_ = lookup[seps]
convert.locale_ = locale_
except (KeyError,) as e:
convert.locale_ = None
return "unexpected seps:%s" % seps
try:
return parse_decimal(input_, locale=locale_)
except (Exception,) as e:
return e
lookup = {}
#train your data
for input_, exp in traindata:
setup(input_, exp, lookup)
#once it trained you know which locales to use
print(data)
for input_, exp in data:
got = convert(input_, lookup)
# print (input_)
msg = "%s => %s with local:%s:" % (input_, got, convert.locale_)
if exp == got:
print("\n success : " + msg)
else:
print("\n failure : " + msg)
print(lookup)
выход:
[('1.234.345,00', Decimal('1234345.00')), ('1,234,345.00', Decimal('1234345.00')), ('345', Decimal('345.00')), ('345,00', Decimal('345.00')), ('345.00', Decimal('345.00')), ('746', Decimal('746.00'))]
success : 1.234.345,00 => 1234345.00 with local:is_is:
success : 1,234,345.00 => 1234345.00 with local:ko_kr.euc:
success : 345 => 345 with local:ko_kr.euc:
success : 345,00 => 345.00 with local:is_is:
success : 345.00 => 345.00 with local:ko_kr.euc:
success : 746 => 746 with local:ko_kr.euc:
{',.': 'ko_kr.euc', '': 'ko_kr.euc', '.,': 'is_is'}
Ответ 3
В вашем вопросе есть две части:
- Как я могу разобрать "1.234.345,00" в общем виде?
- Как я могу легко найти локаль, связанную с "1.234.345,00"?
Вы можете использовать удивительную библиотеку Babel для обоих.
Как я могу разобрать "1.234.345,00" в общем виде?
Один язык, связанный с a .
разделитель тысяч и ,
десятичный разделитель ger_de
, для немецкого языка.
Чтобы проанализировать его, просто используйте
>>> from babel.numbers import parse_decimal
>>> parse_decimal('1.234.345,00', locale='ger_de')
Decimal('1234345.00')
Как я могу легко найти локаль, связанную с "1.234.345,00"?
Используйте эту процедуру, которая проверяет строку для разбора против ожидаемого значения для всех локалей и возвращает совместимые:
import locale
from babel.numbers import parse_decimal
from decimal import Decimal
def get_compatible_locales(string_to_parse, expected_decimal):
compatible_aliases = []
for alias in locale.locale_alias:
try:
parsed_decimal = parse_decimal(string_to_parse, locale=alias)
if parsed_decimal == expected_decimal:
compatible_aliases.append(alias)
except Exception:
continue
return compatible_aliases
Для вашего примера:
>>> print(get_compatible_locales('1.234.345,00', Decimal('1234345')))
['ar_dz', 'ar_lb', 'ar_ly', 'ar_ma', 'ar_tn', 'ast_es', 'az', 'az_az', 'az_az.iso88599e', 'bs', 'bs_ba', 'ca', 'ca_ad', 'ca_es', '[email protected]', 'ca_fr', 'ca_it', 'da', 'da_dk', 'de', 'de_at', 'de_be', 'de_de', 'de_lu', 'el', 'el_cy', 'el_gr', '[email protected]', 'en_be', 'en_dk', 'es', 'es_ar', 'es_bo', 'es_cl', 'es_co', 'es_ec', 'es_es', 'es_py', 'es_uy', 'es_ve', 'eu', 'eu_es', 'fo', 'fo_fo', 'fr_lu', 'fy_nl', 'ger_de', 'gl', 'gl_es', 'hr', 'hr_hr', 'hsb_de', 'id', 'id_id', 'in', 'in_id', 'is', 'is_is', 'it', 'it_it', 'kl', 'kl_gl', 'km_kh', 'lb_lu', 'lo', 'lo_la', 'lo_la.cp1133', 'lo_la.ibmcp1133', 'lo_la.mulelao1', 'mk', 'mk_mk', 'nl', 'nl_aw', 'nl_be', 'nl_nl', 'ps_af', 'pt', 'pt_br', 'ro', 'ro_ro', 'rw', 'rw_rw', 'sl', 'sl_si', 'sr', '[email protected]', '[email protected]', 'sr_cs', '[email protected]', '[email protected]', 'sr_me', 'sr_rs', '[email protected]', 'sr_yu', '[email protected]', 'sr_yu.iso88592', 'sr_yu.iso88595', '[email protected]', '[email protected]', 'sr_yu.utf8', '[email protected]', '[email protected]', 'tr', 'tr_cy', 'tr_tr', 'vi', 'vi_vn', 'vi_vn.tcvn', 'vi_vn.tcvn5712', 'vi_vn.viscii', 'vi_vn.viscii111', 'wo_sn']
Бонус: как я могу получить удобочитаемую версию этих локалей?
Используйте следующую процедуру, где my_locale
должен быть вашим собственным my_locale
:
from babel import Locale
def get_display_name(alias, my_locale='en_US'):
l = Locale.parse(alias)
return l.get_display_name(my_locale)
Вы можете использовать его так:
>>> print({loc: get_display_name(loc) for loc in locales})
{'ar_dz': 'Arabic (Algeria)', 'ar_lb': 'Arabic (Lebanon)', 'ar_ly': 'Arabic (Libya)', 'ar_ma': 'Arabic (Morocco)', 'ar_tn': 'Arabic (Tunisia)', 'ast_es': 'Asturian (Spain)', 'az': 'Azerbaijani', 'az_az': 'Azerbaijani (Latin, Azerbaijan)', 'az_az.iso88599e': 'Azerbaijani (Latin, Azerbaijan)', 'bs': 'Bosnian', 'bs_ba': 'Bosnian (Latin, Bosnia & Herzegovina)', 'ca': 'Catalan', 'ca_ad': 'Catalan (Andorra)', 'ca_es': 'Catalan (Spain)', '[email protected]': 'Catalan (Spain)', 'ca_fr': 'Catalan (France)', 'ca_it': 'Catalan (Italy)', 'da': 'Danish', 'da_dk': 'Danish (Denmark)', 'de': 'German', 'de_at': 'German (Austria)', 'de_be': 'German (Belgium)', 'de_de': 'German (Germany)', 'de_lu': 'German (Luxembourg)', 'el': 'Greek', 'el_cy': 'Greek (Cyprus)', 'el_gr': 'Greek (Greece)', '[email protected]': 'Greek (Greece)', 'en_be': 'English (Belgium)', 'en_dk': 'English (Denmark)', 'es': 'Spanish', 'es_ar': 'Spanish (Argentina)', 'es_bo': 'Spanish (Bolivia)', 'es_cl': 'Spanish (Chile)', 'es_co': 'Spanish (Colombia)', 'es_ec': 'Spanish (Ecuador)', 'es_es': 'Spanish (Spain)', 'es_py': 'Spanish (Paraguay)', 'es_uy': 'Spanish (Uruguay)', 'es_ve': 'Spanish (Venezuela)', 'eu': 'Basque', 'eu_es': 'Basque (Spain)', 'fo': 'Faroese', 'fo_fo': 'Faroese (Faroe Islands)', 'fr_lu': 'French (Luxembourg)', 'fy_nl': 'Western Frisian (Netherlands)', 'ger_de': 'German (Germany)', 'gl': 'Galician', 'gl_es': 'Galician (Spain)', 'hr': 'Croatian', 'hr_hr': 'Croatian (Croatia)', 'hsb_de': 'Upper Sorbian (Germany)', 'id': 'Indonesian', 'id_id': 'Indonesian (Indonesia)', 'in': 'Indonesian (Indonesia)', 'in_id': 'Indonesian (Indonesia)', 'is': 'Icelandic', 'is_is': 'Icelandic (Iceland)', 'it': 'Italian', 'it_it': 'Italian (Italy)', 'kl': 'Kalaallisut', 'kl_gl': 'Kalaallisut (Greenland)', 'km_kh': 'Khmer (Cambodia)', 'lb_lu': 'Luxembourgish (Luxembourg)', 'lo': 'Lao', 'lo_la': 'Lao (Laos)', 'lo_la.cp1133': 'Lao (Laos)', 'lo_la.ibmcp1133': 'Lao (Laos)', 'lo_la.mulelao1': 'Lao (Laos)', 'mk': 'Macedonian', 'mk_mk': 'Macedonian (Macedonia)', 'nl': 'Dutch', 'nl_aw': 'Dutch (Aruba)', 'nl_be': 'Dutch (Belgium)', 'nl_nl': 'Dutch (Netherlands)', 'ps_af': 'Pashto (Afghanistan)', 'pt': 'Portuguese', 'pt_br': 'Portuguese (Brazil)', 'ro': 'Romanian', 'ro_ro': 'Romanian (Romania)', 'rw': 'Kinyarwanda', 'rw_rw': 'Kinyarwanda (Rwanda)', 'sl': 'Slovenian', 'sl_si': 'Slovenian (Slovenia)', 'sr': 'Serbian', '[email protected]': 'Serbian', '[email protected]': 'Serbian', 'sr_cs': 'Serbian (Cyrillic, Serbia)', '[email protected]': 'Serbian (Cyrillic, Serbia)', '[email protected]': 'Serbian (Cyrillic, Serbia)', 'sr_me': 'Serbian (Latin, Montenegro)', 'sr_rs': 'Serbian (Cyrillic, Serbia)', '[email protected]': 'Serbian (Cyrillic, Serbia)', 'sr_yu': 'Serbian (Cyrillic, Serbia)', '[email protected]': 'Serbian (Cyrillic, Serbia)', 'sr_yu.iso88592': 'Serbian (Cyrillic, Serbia)', 'sr_yu.iso88595': 'Serbian (Cyrillic, Serbia)', '[email protected]': 'Serbian (Cyrillic, Serbia)', '[email protected]': 'Serbian (Cyrillic, Serbia)', 'sr_yu.utf8': 'Serbian (Cyrillic, Serbia)', '[email protected]': 'Serbian (Cyrillic, Serbia)', '[email protected]': 'Serbian (Cyrillic, Serbia)', 'tr': 'Turkish', 'tr_cy': 'Turkish (Cyprus)', 'tr_tr': 'Turkish (Turkey)', 'vi': 'Vietnamese', 'vi_vn': 'Vietnamese (Vietnam)', 'vi_vn.tcvn': 'Vietnamese (Vietnam)', 'vi_vn.tcvn5712': 'Vietnamese (Vietnam)', 'vi_vn.viscii': 'Vietnamese (Vietnam)', 'vi_vn.viscii111': 'Vietnamese (Vietnam)', 'wo_sn': 'Wolof (Senegal)'}
Попробуйте прямо сейчас!