只需在 python 中使用秒差距
Simply using parsec in python
我正在查看这个库,它几乎没有文档:
https://pythonhosted.org/parsec/#examples
我understand there are alternatives,但我想使用这个库。
我要解析以下字符串:
mystr = """
<kv>
key1: "string"
key2: 1.00005
key3: [1,2,3]
</kv>
<csv>
date,windspeed,direction
20190805,22,NNW
20190805,23,NW
20190805,20,NE
</csv>"""
虽然我想解析整个内容,但我还是满足于只抓住 <tags>
。我有:
>>> import parsec
>>> tag_start = parsec.Parser(lambda x: x == "<")
>>> tag_end = parsec.Parser(lambda x: x == ">")
>>> tag_name = parsec.Parser(parsec.Parser.compose(parsec.many1, parsec.letter))
>>> tag_open = parsec.Parser(parsec.Parser.joint(tag_start, tag_name, tag_end))
好的,看起来不错。现在开始使用它:
>>> tag_open.parse(mystr)
Traceback (most recent call last):
...
TypeError: <lambda>() takes 1 positional argument but 2 were given
这失败了。恐怕我什至不明白我的 lambda 表达式给出两个参数是什么意思,显然是 1。我该如何继续?
我对所有奖励积分的最佳期望输出是:
[
{"type": "tag",
"name" : "kv",
"values" : [
{"key1" : "string"},
{"key2" : 1.00005},
{"key3" : [1,2,3]}
]
},
{"type" : "tag",
"name" : "csv",
"values" : [
{"date" : 20190805, "windspeed" : 22, "direction": "NNW"}
{"date" : 20190805, "windspeed" : 23, "direction": "NW"}
{"date" : 20190805, "windspeed" : 20, "direction": "NE"}
]
}
我想在这个问题中理解的输出是使用上述函数生成开始和结束标记:
[
{"tag": "kv"},
{"tag" : "csv"}
]
并且能够简单地从混乱的混合文本条目中解析出任意 xml 类标签。
由于解析器需要一个具有两个替代结果(和两个参数)的函数,您可以考虑破坏函数参数而不是尝试使用内联函数定义来实现它(lambda
)
A Parser is an object that wraps a function to do the parsing work.
Arguments of the function should be a string to be parsed and the
index on which to begin parsing. The function should return either
Value.success(next_index, value) if parsing successfully, or
Value.failure(index, expected) on the failure
但是,如果你想使用 lambda 表达式,你可以指定两个必需的参数,也许可以使用 lambda,例如:(不确定 Value.success
或 Value.failure
在不阅读的情况下如何工作通过文档。)
lamdba x,y: Value.Success(y+1, x) if x[y] == "<" else Value.failure(y, x)
正如其他人指出的那样,解析函数需要接受两个参数。
多个输入参数的语法是:lambda x, y: ...
不幸的是,lambda
不适合以这种方式构建 parsec 解析器,因为您需要 return 一个 parsec.Value
类型而不是布尔值,因此它很快就会失去简洁性。
parsec 的设计需要 Parser
在不了解任何其他解析器的情况下独立地对输入流进行操作。为了有效地做到这一点,解析器必须管理输入字符串的索引位置。他们在消耗一些令牌后收到起始索引位置和 return 下一个位置。这就是 parsec.Value
被 returned(布尔值,输出索引)并且需要输入索引和输入字符串的原因。
这是一个使用 <
令牌的基本示例,用于说明:
import parsec
def parse_start_tag(stream, index):
if stream[0] == '<':
return parsec.Value.success(index + 1, stream[1:])
else:
return parsec.Value.failure(index, '<')
tag_open = parsec.Parser(parse_start_tag)
print(tag_open.parse("<tag>")) # prints: "tag>"
print(tag_open.parse("tag>")) # fails: "expected <"
根据测试,解析字符串的正确方法如下:
from parsec import *
possible_chars = letter() | space() | one_of('/.,:"[]') | digit()
parser = many(many(possible_chars) + string("<") >> mark(many(possible_chars)) << string(">"))
parser.parse(mystr)
# [((1, 1), ['k', 'v'], (1, 3)), ((5, 1), ['/', 'k', 'v'], (5, 4)), ((6, 1), ['c', 's', 'v'], (6, 4)), ((11, 1), ['/', 'c', 's', 'v'], (11, 5))]
构造parser
:
为了方便起见,我们先定义要匹配的字符。 parsec
提供多种类型:
letter()
:匹配任意字母字符,
string(str)
:匹配任意指定字符串str
,
space()
:匹配任意空白字符,
spaces()
:匹配多个空白字符,
digit()
:匹配任意数字,
eof()
:匹配字符串的EOF标志,
regex(pattern)
:匹配提供的正则表达式模式,
one_of(str)
:匹配所提供字符串中的任意字符,
none_of(str)
: 匹配不在提供的字符串中的字符。
根据文档,我们可以用运算符将它们分开:
|
:这个组合器实现了选择。解析器q 首先应用 p。
如果成功,p 的值是 returned。
如果 p 在没有消耗任何输入 的情况下失败 ,则会尝试解析器 q。
注意:没有回溯,
+
:将两个或多个解析器合并为一个。 Return 两个结果的总和
来自这两个解析器。
^
:有回溯的选择。每当任意时使用此组合器
需要向前看。解析器 p || q 首先应用 p,如果成功,
p 的值是 returned。如果 p 失败,它就假装它没有消费
任何输入,然后尝试解析器 q。
<<
:以指定的解析器结束,最后解析器消耗了
结束标志,
<
:以指定的parser结束,最后parser还没有消费
任何输入,
>>
:依次组合两个动作,丢弃产生的任何值
首先,
mark(p)
:标记解析器结果的行列信息p
.
然后有多个"combinators":
times(p, mint, maxt=None)
:重复解析器 p
从 mint
到 maxt
次,
count(p,n)
:重复解析器 p
n
次。如果 n
小于或等于零,则解析器等于 return 空列表,
(p, default_value=None)
:使解析器可选。如果成功,return 结果,否则 return default_value
静默,不引发任何异常。如果未提供 default_value
None
,则改为 returned,
many(p)
: 重复解析器p
从不到无限多次,
many1(p)
:重复解析器p
至少一次,
separated(p, sep, mint, maxt=None, end=None)
: ,
sepBy(p, sep)
:解析零次或多次出现的解析器p
,用分隔符sep
、
分隔
sepBy1(p, sep)
:解析至少一次出现的解析器p
,用分隔符sep
、
分隔
endBy(p, sep)
:解析零次或多次出现的p
,由sep
、
分隔和结束
endBy1(p, sep)
:解析至少一次出现的p
,由sep
、
分隔和结束
sepEndBy(p, sep)
:解析 p
的零次或多次出现,由 sep
、
分隔并可选择结束
sepEndBy1(p, sep)
:解析至少一次出现的 p
,由 sep
分隔并可选择结束。
使用所有这些,我们有一个解析器匹配许多 possible_chars
,然后是 <
,然后我们标记 possible_chars
直到 >
.
我鼓励您使用这些组合器定义您自己的解析器,而不是直接构造 Parser
。
如果你想通过包装一个函数来构造一个 Parser
,如文档所述,fn
应该接受两个参数,第一个是文本,第二个是当前位置。 fn
应该 return 通过 Value.success
或 Value.failure
得到 Value
,而不是布尔值。你可以在这个包的 parsec/__init__.py
中 grep @Parser
来找到更多关于它如何工作的例子。
对于描述中的案例,您可以按如下方式定义解析器:
from parsec import *
spaces = regex(r'\s*', re.MULTILINE)
name = regex(r'[_a-zA-Z][_a-zA-Z0-9]*')
tag_start = spaces >> string('<') >> name << string('>') << spaces
tag_stop = spaces >> string('</') >> name << string('>') << spaces
@generate
def header_kv():
key = yield spaces >> name << spaces
yield string(':')
value = yield spaces >> regex('[^\n]+')
return {key: value}
@generate
def header():
tag_name = yield tag_start
values = yield sepBy(header_kv, string('\n'))
tag_name_end = yield tag_stop
assert tag_name == tag_name_end
return {
'type': 'tag',
'name': tag_name,
'values': values
}
@generate
def body():
tag_name = yield tag_start
values = yield sepBy(sepBy1(regex(r'[^\n<,]+'), string(',')), string('\n'))
tag_name_end = yield tag_stop
assert tag_name == tag_name_end
return {
'type': 'tag',
'name': tag_name,
'values': values
}
parser = header + body
如果你 运行 parser.parse(mystr)
,它会产生
({'type': 'tag',
'name': 'kv',
'values': [{'key1': '"string"'},
{'key2': '1.00005'},
{'key3': '[1,2,3]'}]},
{'type': 'tag',
'name': 'csv',
'values': [['date', 'windspeed', 'direction'],
['20190805', '22', 'NNW'],
['20190805', '23', 'NW'],
['20190805', '20', 'NE']]}
)
您可以优化上述代码中 values
的定义,以得到您想要的准确形式的结果。
我正在查看这个库,它几乎没有文档: https://pythonhosted.org/parsec/#examples
我understand there are alternatives,但我想使用这个库。
我要解析以下字符串:
mystr = """
<kv>
key1: "string"
key2: 1.00005
key3: [1,2,3]
</kv>
<csv>
date,windspeed,direction
20190805,22,NNW
20190805,23,NW
20190805,20,NE
</csv>"""
虽然我想解析整个内容,但我还是满足于只抓住 <tags>
。我有:
>>> import parsec
>>> tag_start = parsec.Parser(lambda x: x == "<")
>>> tag_end = parsec.Parser(lambda x: x == ">")
>>> tag_name = parsec.Parser(parsec.Parser.compose(parsec.many1, parsec.letter))
>>> tag_open = parsec.Parser(parsec.Parser.joint(tag_start, tag_name, tag_end))
好的,看起来不错。现在开始使用它:
>>> tag_open.parse(mystr)
Traceback (most recent call last):
...
TypeError: <lambda>() takes 1 positional argument but 2 were given
这失败了。恐怕我什至不明白我的 lambda 表达式给出两个参数是什么意思,显然是 1。我该如何继续?
我对所有奖励积分的最佳期望输出是:
[
{"type": "tag",
"name" : "kv",
"values" : [
{"key1" : "string"},
{"key2" : 1.00005},
{"key3" : [1,2,3]}
]
},
{"type" : "tag",
"name" : "csv",
"values" : [
{"date" : 20190805, "windspeed" : 22, "direction": "NNW"}
{"date" : 20190805, "windspeed" : 23, "direction": "NW"}
{"date" : 20190805, "windspeed" : 20, "direction": "NE"}
]
}
我想在这个问题中理解的输出是使用上述函数生成开始和结束标记:
[
{"tag": "kv"},
{"tag" : "csv"}
]
并且能够简单地从混乱的混合文本条目中解析出任意 xml 类标签。
由于解析器需要一个具有两个替代结果(和两个参数)的函数,您可以考虑破坏函数参数而不是尝试使用内联函数定义来实现它(lambda
)
A Parser is an object that wraps a function to do the parsing work. Arguments of the function should be a string to be parsed and the index on which to begin parsing. The function should return either Value.success(next_index, value) if parsing successfully, or Value.failure(index, expected) on the failure
但是,如果你想使用 lambda 表达式,你可以指定两个必需的参数,也许可以使用 lambda,例如:(不确定 Value.success
或 Value.failure
在不阅读的情况下如何工作通过文档。)
lamdba x,y: Value.Success(y+1, x) if x[y] == "<" else Value.failure(y, x)
正如其他人指出的那样,解析函数需要接受两个参数。
多个输入参数的语法是:lambda x, y: ...
不幸的是,lambda
不适合以这种方式构建 parsec 解析器,因为您需要 return 一个 parsec.Value
类型而不是布尔值,因此它很快就会失去简洁性。
parsec 的设计需要 Parser
在不了解任何其他解析器的情况下独立地对输入流进行操作。为了有效地做到这一点,解析器必须管理输入字符串的索引位置。他们在消耗一些令牌后收到起始索引位置和 return 下一个位置。这就是 parsec.Value
被 returned(布尔值,输出索引)并且需要输入索引和输入字符串的原因。
这是一个使用 <
令牌的基本示例,用于说明:
import parsec
def parse_start_tag(stream, index):
if stream[0] == '<':
return parsec.Value.success(index + 1, stream[1:])
else:
return parsec.Value.failure(index, '<')
tag_open = parsec.Parser(parse_start_tag)
print(tag_open.parse("<tag>")) # prints: "tag>"
print(tag_open.parse("tag>")) # fails: "expected <"
根据测试,解析字符串的正确方法如下:
from parsec import *
possible_chars = letter() | space() | one_of('/.,:"[]') | digit()
parser = many(many(possible_chars) + string("<") >> mark(many(possible_chars)) << string(">"))
parser.parse(mystr)
# [((1, 1), ['k', 'v'], (1, 3)), ((5, 1), ['/', 'k', 'v'], (5, 4)), ((6, 1), ['c', 's', 'v'], (6, 4)), ((11, 1), ['/', 'c', 's', 'v'], (11, 5))]
构造parser
:
为了方便起见,我们先定义要匹配的字符。 parsec
提供多种类型:
letter()
:匹配任意字母字符,string(str)
:匹配任意指定字符串str
,space()
:匹配任意空白字符,spaces()
:匹配多个空白字符,digit()
:匹配任意数字,eof()
:匹配字符串的EOF标志,regex(pattern)
:匹配提供的正则表达式模式,one_of(str)
:匹配所提供字符串中的任意字符,none_of(str)
: 匹配不在提供的字符串中的字符。
根据文档,我们可以用运算符将它们分开:
|
:这个组合器实现了选择。解析器q 首先应用 p。 如果成功,p 的值是 returned。 如果 p 在没有消耗任何输入 的情况下失败 ,则会尝试解析器 q。 注意:没有回溯,+
:将两个或多个解析器合并为一个。 Return 两个结果的总和 来自这两个解析器。^
:有回溯的选择。每当任意时使用此组合器 需要向前看。解析器 p || q 首先应用 p,如果成功, p 的值是 returned。如果 p 失败,它就假装它没有消费 任何输入,然后尝试解析器 q。<<
:以指定的解析器结束,最后解析器消耗了 结束标志,<
:以指定的parser结束,最后parser还没有消费 任何输入,>>
:依次组合两个动作,丢弃产生的任何值 首先,mark(p)
:标记解析器结果的行列信息p
.
然后有多个"combinators":
times(p, mint, maxt=None)
:重复解析器p
从mint
到maxt
次,count(p,n)
:重复解析器p
n
次。如果n
小于或等于零,则解析器等于 return 空列表,(p, default_value=None)
:使解析器可选。如果成功,return 结果,否则 returndefault_value
静默,不引发任何异常。如果未提供default_value
None
,则改为 returned,many(p)
: 重复解析器p
从不到无限多次,many1(p)
:重复解析器p
至少一次,separated(p, sep, mint, maxt=None, end=None)
: ,sepBy(p, sep)
:解析零次或多次出现的解析器p
,用分隔符sep
、 分隔
sepBy1(p, sep)
:解析至少一次出现的解析器p
,用分隔符sep
、 分隔
endBy(p, sep)
:解析零次或多次出现的p
,由sep
、 分隔和结束
endBy1(p, sep)
:解析至少一次出现的p
,由sep
、 分隔和结束
sepEndBy(p, sep)
:解析p
的零次或多次出现,由sep
、 分隔并可选择结束
sepEndBy1(p, sep)
:解析至少一次出现的p
,由sep
分隔并可选择结束。
使用所有这些,我们有一个解析器匹配许多 possible_chars
,然后是 <
,然后我们标记 possible_chars
直到 >
.
我鼓励您使用这些组合器定义您自己的解析器,而不是直接构造 Parser
。
如果你想通过包装一个函数来构造一个 Parser
,如文档所述,fn
应该接受两个参数,第一个是文本,第二个是当前位置。 fn
应该 return 通过 Value.success
或 Value.failure
得到 Value
,而不是布尔值。你可以在这个包的 parsec/__init__.py
中 grep @Parser
来找到更多关于它如何工作的例子。
对于描述中的案例,您可以按如下方式定义解析器:
from parsec import *
spaces = regex(r'\s*', re.MULTILINE)
name = regex(r'[_a-zA-Z][_a-zA-Z0-9]*')
tag_start = spaces >> string('<') >> name << string('>') << spaces
tag_stop = spaces >> string('</') >> name << string('>') << spaces
@generate
def header_kv():
key = yield spaces >> name << spaces
yield string(':')
value = yield spaces >> regex('[^\n]+')
return {key: value}
@generate
def header():
tag_name = yield tag_start
values = yield sepBy(header_kv, string('\n'))
tag_name_end = yield tag_stop
assert tag_name == tag_name_end
return {
'type': 'tag',
'name': tag_name,
'values': values
}
@generate
def body():
tag_name = yield tag_start
values = yield sepBy(sepBy1(regex(r'[^\n<,]+'), string(',')), string('\n'))
tag_name_end = yield tag_stop
assert tag_name == tag_name_end
return {
'type': 'tag',
'name': tag_name,
'values': values
}
parser = header + body
如果你 运行 parser.parse(mystr)
,它会产生
({'type': 'tag',
'name': 'kv',
'values': [{'key1': '"string"'},
{'key2': '1.00005'},
{'key3': '[1,2,3]'}]},
{'type': 'tag',
'name': 'csv',
'values': [['date', 'windspeed', 'direction'],
['20190805', '22', 'NNW'],
['20190805', '23', 'NW'],
['20190805', '20', 'NE']]}
)
您可以优化上述代码中 values
的定义,以得到您想要的准确形式的结果。