Я хотел бы знать, если есть способ узнать, является ли строка с параметром допустимым и подсчитать, сколько полей внутри. Я бы предпочел нативную функцию (и) Python, но ничего не нашел по этому поводу. Допустим, эта функция называется count_variables .

Я бы:

count_variables("Test") # -> 0

count_variables("Test {0} {1}") # -> 2

count_variables("Test {0} {2}") # -> raise error {1} is missing

count_variables("Test {} {}") # -> 2

count_variables("Test{ {} {}") # -> raise error { is not escaped

count_variables("Test {} {0}") # -> raise error cannot switch from automatic field numbering to manual field 

Я использую Python 2.7

Как уже упоминалось @ dot.Py, более легкая функция is_valid может быть проще. Только проверка строки без обязательных параметров

is_valid("Test") # -> True

is_valid("Test {0} {2}") # -> False

...

Спасибо за вашу помощь.

-1
M07 12 Янв 2017 в 19:36

4 ответа

Лучший ответ

Моя идея состоит в том, чтобы использовать string.Formatter.parse для подсчета переменных, а затем на самом деле попробовать форматирование именно с таким количеством переменных.

Это работает для примеров, перечисленных в вопросе, но в остальном не очень хорошо проверено.

import string

def vcount(fmt):
    try:
        cnt = sum(1 for text, name, spec, conv in string.Formatter().parse(fmt) if name is not None)
        fmt.format(*range(cnt))
    except Exception as err:
        print("error: {}".format(err))
        return None # or raise ValueError(err)
    print(cnt)
    return cnt 

vcount("Test") # -> 0
vcount("Test {0} {1}") # -> 2
vcount("Test {0} {2}") # -> raise error
vcount("Test {} {}") # -> 2
vcount("Test{ {} {}") # -> raise error
vcount("Test {} {0}") # -> raise error

ОБНОВЛЕНИЕ: другой подход, не эквивалентный оригинальному ответу. Смотрите комментарии. Сообщение об ошибке для неверного ввода может сбить с толку.

def vcount(fmt):
    try:
        names = [name for text, name, spec, conv in string.Formatter().parse(fmt) if name is not None]
        if all(name == "" for name in names):
            # unnumbered fields "{} {}"
            cnt = len(names)
        else:
            # numbered "{0} {1} {2} {0}"
            cnt = 1 + max(int(name) for name in names)
        fmt.format(*range(cnt))
    except Exception as err:
        print("error: {}".format(err))
        return None # or raise ValueError(err)
    print(cnt)
    return cnt 
2
VPfB 13 Янв 2017 в 19:22

Вы можете создать объект string.Format и использовать его метод parse, чтобы разбить строку на кортежи (literal_text, field_name, format_spec, conversion). При этом будут обнаружены некоторые ошибки, такие как неэкранированные {, но другие, такие как неправильно пронумерованные поля, не будут обнаружены.

Мне кажется, что вы можете создать дочерний элемент string.Format, который будет возвращать фиктивные данные для своих различных вызовов, по мере необходимости записывая детали. Затем вы поймаете все ошибки. Это должно быть проще, чем разбираться самому.

Что касается подсчета количества ошибок формата, это будет сделано:

import string

def count_variables(fmtstr):
    parser = string.Formatter().parse(fmtstr)
    items = []
    while True:
        try:
            item = next(parser)
            items.append(item)
            literal_text, field_name, format_spec, conversion = item
            # analyze here...
        except ValueError as e:
            retval = e
            break
        except StopIteration:
            retval = len(items)
            break
    print fmtstr + ':', retval
    return retval
2
tdelaney 12 Янв 2017 в 17:24

В дополнение к этому ответу и для обработки следующего случая:

vcount("Test {0} {1} {0} ") # -> 3 (Should be 2)

Я предлагаю это решение на основе ответа @VPfB

def count_and_check_fields(string_format):
    try:
        unnamed_fields_count = 0
        named_fields = set()
        for literal_text, field_name, format_spec, conversion in string.Formatter().parse(string_format):
            if field_name is not None:
                if field_name:
                    named_fields.add(field_name)
                else:
                    unnamed_fields_count += 1

        fields_count = len(named_fields) + unnamed_fields_count
        string_format.format(*range(fields_count))

        return fields_count, None
    except Exception as err:
        return None, err.message

count_and_check_fields("Test {0} {1} {0} ") # -> 2
count_and_check_fields("Test {} {} {} ") # -> 3
0
Community 23 Май 2017 в 12:33

Я не знаю, есть ли встроенный способ, но я сам реализовал решение. Я протестировал его под Python 3.5 и Python 2.7. Это «правильно», поскольку оно проходит тестовые примеры, которые вы предоставили:

< Сильный > Реализация

import re
import unittest


class Numbering:
    NONE = 0
    MANUAL = 1
    AUTOMATIC = 2


def consecutive_variables(variables):
    sorted_variables = sorted(variables)
    return all(a == b - 1 for a, b in zip(sorted_variables[:-1], sorted_variables[1:]))


def count_variables(data):
    numbering = Numbering.NONE
    last_variable = 0
    variables = []

    for i in range(len(data)):
        c = data[i]

        if c == '{':
            match = re.match(r'(\d|^{|^})*?(?=})', data[i + 1:])

            if not match:
                raise ValueError('Invalid variable formatting')

            variable_body = match.group(0)

            if variable_body == '':
                if numbering == Numbering.MANUAL:
                    raise ValueError('Cannot switch from manual to automatic numbering')

                numbering = Numbering.AUTOMATIC
                variables.append(last_variable)
                last_variable += 1
            else:
                if numbering == Numbering.AUTOMATIC:
                    raise ValueError('Cannot switch from automatic to manual numbering')

                numbering = Numbering.MANUAL
                variables.append(int(variable_body))

            i += len(variable_body) + 1
            assert data[i] == '}'

    if not consecutive_variables(variables):
        raise ValueError('Variables are not consecutive')

    return len(variables)

< Сильный > Испытания

class TestCountVariables(unittest.TestCase):
    def test_1(self):
        self.assertEqual(count_variables("Test"), 0)

    def test_2(self):
        self.assertEqual(count_variables("Test {0} {1}"), 2)

    def test_3(self):
        with self.assertRaises(ValueError):
            count_variables("Test {0} {2}")

    def test_4(self):
        self.assertEqual(count_variables("Test {} {}"), 2)

    def test_5(self):
        with self.assertRaises(ValueError):
            count_variables("Test{ {} {}")

    def test_6(self):
        with self.assertRaises(ValueError):
            count_variables("Test {} {0}")


if __name__ == '__main__':
    unittest.main()

< Сильный > Выход

......
----------------------------------------------------------------------
Ran 6 tests in 0.000s

OK
2
Tagc 12 Янв 2017 в 17:31