Разбирайте валюту в числах в Python

Я только что узнал из Форматирование чисел как валюты в Python, что модуль Python babel предоставляет _ 1_ для форматирования чисел как валюты. Например,

from babel.numbers import format_currency

s = format_currency(123456.789, 'USD', locale='en_US')  # u'$123,456.79'
s = format_currency(123456.789, 'EUR', locale='fr_FR')  # u'123\xa0456,79\xa0\u20ac'

Как насчет обратного перехода от валюты к числам, например $123,456,789.00 -> 123456789? babel предоставляет babel.numbers.parse_number для анализа местных номеров, но я этого не сделал. не нашел что-то вроде parse_currency. Итак, каков идеальный способ разложить местную валюту на числа?


Я прошел через Python: удаление символов, кроме цифр, из строки.

# Way 1
import string
all=string.maketrans('','')
nodigs=all.translate(all, string.digits)

s = '$123,456.79'
n = s.translate(all, nodigs)    # 12345679, lost `.`

# Way 2
import re
n = re.sub("\D", "", s)         # 12345679

Не заботится о десятичном разделителе ..


Удалите из строки все нечисловые символы, кроме . (см. здесь),

import re

# Way 1:
s = '$123,456.79'
n = re.sub("[^0-9|.]", "", s)   # 123456.79

# Way 2:
non_decimal = re.compile(r'[^\d.]+')
s = '$123,456.79'
n = non_decimal.sub('', s)      # 123456.79

Он обрабатывает десятичный разделитель ..


Но вышеперечисленные решения не работают, например, при обращении к

from babel.numbers import format_currency
s = format_currency(123456.789, 'EUR', locale='fr_FR')  # u'123\xa0456,79\xa0\u20ac'
new_s = s.encode('utf-8') # 123 456,79 €

Как видите, формат валюты различается. Каков идеальный способ разложения валюты на числа в целом?


person SparkAndShine    schedule 01.06.2016    source источник
comment
почему бы вам не проголосовали без причины?   -  person Jay Wong    schedule 02.06.2016
comment
@leekaiinthesky, валюта может содержать , или ..   -  person SparkAndShine    schedule 02.06.2016
comment
@ TigerhawkT3 Точно не дубликат, поскольку . по-прежнему имеет значение.   -  person leekaiinthesky    schedule 02.06.2016
comment
@sparkandshine Вы также хотите ввести локаль? Или вы будете знать, какие символы вы хотите удалить (в этом случае достаточно регулярных выражений)?   -  person leekaiinthesky    schedule 02.06.2016
comment
@ TigerhawkT3, я отредактировал свой вопрос и процитировал упомянутый вами пост. Пожалуйста, проверьте это.   -  person SparkAndShine    schedule 02.06.2016
comment
Это определенно не дубликат, переход от валюты к десятичным числам намного сложнее.   -  person Padraic Cunningham    schedule 02.06.2016


Ответы (2)


Использование babel

В документации babel отмечается, что анализ номеров реализован не полностью да но они проделали большую работу, чтобы получить информацию о валюте в библиотеке. Вы можете использовать get_currency_name() и get_currency_symbol() для получения сведений о валюте, а также все другие get_... функции для получения сведений о обычных числах (десятичная точка, знак минус и т. Д.).

Используя эту информацию, вы можете исключить из строки валюты детали валюты (имя, знак) и группы (например, , в США). Затем вы меняете десятичные числа на те, которые используются C локалью (- для минуса и . для десятичной точки).

В результате получается следующий код (я добавил объект для хранения некоторых данных, которые могут пригодиться при дальнейшей обработке):

import re, os
from babel import numbers as n
from babel.core import default_locale

class AmountInfo(object):
    def __init__(self, name, symbol, value):
        self.name = name
        self.symbol = symbol
        self.value = value

def parse_currency(value, cur):
    decp = n.get_decimal_symbol()
    plus = n.get_plus_sign_symbol()
    minus = n.get_minus_sign_symbol()
    group = n.get_group_symbol()
    name = n.get_currency_name(cur)
    symbol = n.get_currency_symbol(cur)
    remove = [plus, name, symbol, group]
    for token in remove:
        # remove the pieces of information that shall be obvious
        value = re.sub(re.escape(token), '', value)
    # change the minus sign to a LOCALE=C minus
    value = re.sub(re.escape(minus), '-', value)
    # and change the decimal mark to a LOCALE=C decimal point
    value = re.sub(re.escape(decp), '.', value)
    # just in case remove extraneous spaces
    value = re.sub('\s+', '', value)
    return AmountInfo(name, symbol, value)

#cur_loc = os.environ['LC_ALL']
cur_loc = default_locale()
print('locale:', cur_loc)
test = [ (n.format_currency(123456.789, 'USD', locale=cur_loc), 'USD')
       , (n.format_currency(-123456.78, 'PLN', locale=cur_loc), 'PLN')
       , (n.format_currency(123456.789, 'PLN', locale=cur_loc), 'PLN')
       , (n.format_currency(123456.789, 'IDR', locale=cur_loc), 'IDR')
       , (n.format_currency(123456.789, 'JPY', locale=cur_loc), 'JPY')
       , (n.format_currency(-123456.78, 'JPY', locale=cur_loc), 'JPY')
       , (n.format_currency(123456.789, 'CNY', locale=cur_loc), 'CNY')
       , (n.format_currency(-123456.78, 'CNY', locale=cur_loc), 'CNY')
       ]

for v,c in test:
    print('As currency :', c, ':', v.encode('utf-8'))
    info = parse_currency(v, c)
    print('As value    :', c, ':', info.value)
    print('Extra info  :', info.name.encode('utf-8')
                         , info.symbol.encode('utf-8'))

Результат выглядит многообещающим (для США):

$ export LC_ALL=en_US
$ ./cur.py
locale: en_US
As currency : USD : b'$123,456.79'
As value    : USD : 123456.79
Extra info  : b'US Dollar' b'$'
As currency : PLN : b'-z\xc5\x82123,456.78'
As value    : PLN : -123456.78
Extra info  : b'Polish Zloty' b'z\xc5\x82'
As currency : PLN : b'z\xc5\x82123,456.79'
As value    : PLN : 123456.79
Extra info  : b'Polish Zloty' b'z\xc5\x82'
As currency : IDR : b'Rp123,457'
As value    : IDR : 123457
Extra info  : b'Indonesian Rupiah' b'Rp'
As currency : JPY : b'\xc2\xa5123,457'
As value    : JPY : 123457
Extra info  : b'Japanese Yen' b'\xc2\xa5'
As currency : JPY : b'-\xc2\xa5123,457'
As value    : JPY : -123457
Extra info  : b'Japanese Yen' b'\xc2\xa5'
As currency : CNY : b'CN\xc2\xa5123,456.79'
As value    : CNY : 123456.79
Extra info  : b'Chinese Yuan' b'CN\xc2\xa5'
As currency : CNY : b'-CN\xc2\xa5123,456.78'
As value    : CNY : -123456.78
Extra info  : b'Chinese Yuan' b'CN\xc2\xa5'

И он по-прежнему работает в разных регионах (Бразилия примечательна тем, что в качестве десятичного знака используется запятая):

$ export LC_ALL=pt_BR
$ ./cur.py 
locale: pt_BR
As currency : USD : b'US$123.456,79'
As value    : USD : 123456.79
Extra info  : b'D\xc3\xb3lar americano' b'US$'
As currency : PLN : b'-PLN123.456,78'
As value    : PLN : -123456.78
Extra info  : b'Zloti polon\xc3\xaas' b'PLN'
As currency : PLN : b'PLN123.456,79'
As value    : PLN : 123456.79
Extra info  : b'Zloti polon\xc3\xaas' b'PLN'
As currency : IDR : b'IDR123.457'
As value    : IDR : 123457
Extra info  : b'Rupia indon\xc3\xa9sia' b'IDR'
As currency : JPY : b'JP\xc2\xa5123.457'
As value    : JPY : 123457
Extra info  : b'Iene japon\xc3\xaas' b'JP\xc2\xa5'
As currency : JPY : b'-JP\xc2\xa5123.457'
As value    : JPY : -123457
Extra info  : b'Iene japon\xc3\xaas' b'JP\xc2\xa5'
As currency : CNY : b'CN\xc2\xa5123.456,79'
As value    : CNY : 123456.79
Extra info  : b'Yuan chin\xc3\xaas' b'CN\xc2\xa5'
As currency : CNY : b'-CN\xc2\xa5123.456,78'
As value    : CNY : -123456.78
Extra info  : b'Yuan chin\xc3\xaas' b'CN\xc2\xa5'

Следует отметить, что babel имеет некоторые проблемы с кодированием. Это потому, что файлы локали (в locale-data) сами используют другую кодировку. Если вы работаете с валютами, с которыми вы знакомы, это не должно быть проблемой. Но если вы попробуете использовать незнакомую валюту, у вас могут возникнуть проблемы (я только что узнал, что Польша использует iso-8859-2, а не iso-8859-1).

person grochmal    schedule 02.06.2016

Ниже приведен общий анализатор валюты, который не полагается на библиотеку babel.

import numpy as np
import re

def currency_parser(cur_str):
    # Remove any non-numerical characters
    # except for ',' '.' or '-' (e.g. EUR)
    cur_str = re.sub("[^-0-9.,]", '', cur_str)
    # Remove any 000s separators (either , or .)
    cur_str = re.sub("[.,]", '', cur_str[:-3]) + cur_str[-3:]

    if '.' in list(cur_str[-3:]):
        num = float(cur_str)
    elif ',' in list(cur_str[-3:]):
        num = float(cur_str.replace(',', '.'))
    else:
        num = float(cur_str)

    return np.round(num, 2)

Вот скрипт pytest, который проверяет функцию:

import numpy as np
import pytest
import re


def currency_parser(cur_str):
    # Remove any non-numerical characters
    # except for ',' '.' or '-' (e.g. EUR)
    cur_str = re.sub("[^-0-9.,]", '', cur_str)
    # Remove any 000s separators (either , or .)
    cur_str = re.sub("[.,]", '', cur_str[:-3]) + cur_str[-3:]

    if '.' in list(cur_str[-3:]):
        num = float(cur_str)
    elif ',' in list(cur_str[-3:]):
        num = float(cur_str.replace(',', '.'))
    else:
        num = float(cur_str)

    return np.round(num, 2)


@pytest.mark.parametrize('currency_str, expected', [
    (
            '.3', 0.30
    ),
    (
            '1', 1.00
    ),
    (
            '1.3', 1.30
    ),
    (
            '43,324', 43324.00
    ),
    (
            '3,424', 3424.00
    ),
    (
            '-0.00', 0.00
    ),
    (
            'EUR433,432.53', 433432.53
    ),
    (
            '25.675,26 EUR', 25675.26
    ),
    (
            '2.447,93 EUR', 2447.93
    ),
    (
            '-540,89EUR', -540.89
    ),
    (
            '67.6 EUR', 67.60
    ),
    (
            '30.998,63 CHF', 30998.63
    ),
    (
            '0,00 CHF', 0.00
    ),
    (
            '159.750,00 DKK', 159750.00
    ),
    (
            '£ 2.237,85', 2237.85
    ),
    (
            '£ 2,237.85', 2237.85
    ),
    (
            '-1.876,85 SEK', -1876.85
    ),
    (
            '59294325.3', 59294325.30
    ),
    (
            '8,53 NOK', 8.53
    ),
    (
            '0,09 NOK', 0.09
    ),
    (
            '-.9 CZK', -0.9
    ),
    (
            '35.255,40 PLN', 35255.40
    ),
    (
            '-PLN123.456,78', -123456.78
    ),
    (
            'US$123.456,79', 123456.79
    ),
    (
            '-PLN123.456,78', -123456.78
    ),
    (
            'PLN123.456,79', 123456.79
    ),
    (
            'IDR123.457', 123457
    ),
    (
            'JP¥123.457', 123457
    ),
    (
            '-JP\xc2\xa5123.457', -123457
    ),
    (
            'CN\xc2\xa5123.456,79', 123456.79
    ),
    (
            '-CN\xc2\xa5123.456,78', -123456.78
    ),
])
def test_currency_parse(currency_str, expected):
    assert currency_parser(currency_str) == expected
person aj8907    schedule 19.05.2020