【问题标题】:How to count fields in a string?如何计算字符串中的字段?
【发布时间】:2017-01-12 16:36:56
【问题描述】:

我想知道是否有办法知道带参数的字符串是否有效并计算里面有多少字段。我更喜欢本机 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

...

感谢您的帮助。

【问题讨论】:

  • 你自己试过吗?
  • 可能的答案太多,或者对于这种格式来说,好的答案太长了。请添加详细信息以缩小答案范围或隔离可以在几段中回答的问题。
  • 我认为重要的是不要重新发明轮子。这就是为什么我更愿意询问它是否存在本地方法。我的意思是如果我有参数,“格式”方法可能会引发异常,但我没有。我必须在没有参数的情况下进行验证。如果它不存在,我会自己做,但我可能会错过案例。
  • 避免重复发明轮子的一个非常好的方法是花更多时间阅读文档......
  • @martineau。我想您可以向我展示我正在寻找的文档页面。因为你显然比我花更多的时间阅读文档。

标签: python string validation variables field


【解决方案1】:

我的想法是使用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

更新:一种不同的方法,不等同于原始答案。见 cmets。无效输入的错误消息可能令人困惑。

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 

【讨论】:

  • fmt.format(*range(cnt)) 将在非数字格式规范上引发错误。
  • 没错,但实际上这正是我所需要的。 :)
  • @VPfB 使用 count 然后使用 format 方法是个好主意。干得好;)
  • 比我的解决方案更短,我了解了string.Formatter().parse()。 +1
  • @M07 我不确定在这种情况下正确答案应该是什么。您的规范不完整,它以测试用例的形式给出。那么该字符串中返回的字段数应该是 2 还是 3?还是应该提出错误?例如,对于这个字符串 "Test {0} {2}",您需要一个“缺失字段”异常,但其他人可能期望结果值为 3,因为 "Test {0} {2}".format(1, "unused2", 3) 是有效的。
【解决方案2】:

您可以创建一个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

【讨论】:

  • 使用 string.Formatter() 是一个好的开始。感谢您的帮助。
【解决方案3】:

我不知道是否有内置方法,但我自己实现了一个解决方案。我已经在 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

【讨论】:

  • 抱歉,我不想在这个方法中使用正则表达式。感谢您的宝贵时间。
【解决方案4】:

this answer外,还要处理以下情况:

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

【讨论】:

    猜你喜欢
    • 2022-12-21
    • 1970-01-01
    • 1970-01-01
    • 2011-04-29
    • 2019-12-28
    • 1970-01-01
    相关资源
    最近更新 更多