1

次のようなデータがあります。

data = 'person(firstame="bob", lastname="stewart", dob="2010-0206", hobbies=["reading, singing", "drawing"], is_minor=True)'

文法解析規則を次のように書きました。

quotedString.setParseAction(removeQuotes)
list_of_names = delimitedList(quotedString)

person_start = Literal("person(").suppress()
first = Literal("firstname") + Suppress("=") + quotedString
lastname = Literal("lastname") + Suppress("=") + quotedString
dob = Literal("dob") + Suppress("=") + quotedString
hobbies = Literal("hobbies") + Suppress("=[") + list_of_names + Suppress("]")
is_minor = Literal("is_minor") + Suppress("=") + oneOf("True False")
person_end = Suppress(")")
comma = Literal(",").suppress()

my_data = person_start + first +  comma + last + comma + dob +comma + hobbies + comma + is_minor + person_end
result = my_data.parseString(data)

私の質問は3です:

  1. 上記のルールは機能しますが、これを記述するより良い方法があるかどうかを確認したかったのです。
  2. 私のデータでは順序が保証されていないため、姓が名の前に来る可能性があります。どうすればそれを確認できますか。
  3. 最終的に解析した後、すべてをdictとして入れたいので、最初にキー:値:「ボブ」趣味:[「読書」、「歌」、「絵を描く」] ......最善のアプローチは何でしょうか。
4

2 に答える 2

1

投稿されたコードにはいくつかの小さなタイプミスがありました ( firstame="bob"data vs. firstname="bob"lastnamevs. last)。結果を印刷すると、次のようになります。

['firstname', 'bob', 'lastname', 'stewart', 'dob', '2010-0206', 
 'hobbies', 'reading, singing', 'drawing', 'is_minor', 'True']

最初に、list_of_names(以前の SO の質問 pyparsing string of quoted namesから) 可能な値の型として定義したように、ブール値を定義して True/False 値を解析することをお勧めします。oneOf文字列「True」と「False」から実際の Python ブール値に変換する解析アクションを追加しましょう。

boolean_value = oneOf("True False").setParseAction(lambda t: t[0]=='True')

これはremoveQuotes、quotedString での使用に似ています。

これで、解析結果は次のようになります。

['firstname', 'bob', 'lastname', 'stewart', 'dob', '2010-0206', 
 'hobbies', 'reading, singing', 'drawing', 'is_minor', True]

True は文字列ではなく、Python の値であることに注意してくださいTrue(値を引用符で囲む必要はありません)。

質問の最初の部分である、これを dict にする方法について説明します。Pyparsing を使用すると、文法のさまざまな部分の結果名を定義できるため、データが解析された後、それらの値に名前でアクセスできます。これを行うための構文は、メソッドを呼び出すことでしたsetResultsName:

my_data = person_start + first.setResultsName("firstname") + 
          last.setResultsName("lastname") + ...

これはやや面倒で、すべての「.setResultsName」メソッド呼び出しで式が読みにくいことがわかりました。しばらく前に、この構文を受け入れるように API を変更しました。

my_data = person_start + first("firstname") + last("lastname") + ...

ただしfirstlast、 などとして定義したものには、値だけでなく、ラベルも含まれます。

文法を単純化する 1 つの方法は、独自の小さなヘルパー メソッドを作成することnamed_parameterです。

def named_parameter(label, paramtype):
    expr = Literal(label) + Suppress('=') + paramtype(label)
    return expr

は、リテラル文字列と値の結果名のlabel両方を指定するために使用されることに注意してください。これで、文法を次のように定義できます。

first = named_parameter("firstname", quotedString)
last = named_parameter("lastname", quotedString)
dob = named_parameter("dob", quotedString)
hobbies = named_parameter("hobbies", Suppress("[") + list_of_names + Suppress("]"))
is_minor = named_parameter("is_minor", boolean_value)

名前が付けられた値を使用して、解析された結果に Python dict としてアクセスできます。

print result["firstname"]
print result["hobbies"]

プリント:

bob
['reading, singing', 'drawing']

または、必要に応じて、オブジェクト属性表記を使用することもできます。

print result.firstname
print result.hobbies

質問の 2 番目の部分に答えるために、パラメーターが順不同である可能性がある場合の処理​​方法を尋ねました。これを行う最も簡単な方法は、delimitedList再度使用することです。

parameter = first | last | dob | hobbies | is_minor
my_data = person_start + delimitedList(parameter) + person_end

これは厳密なパーサーではありません。すべてのパラメーターを持たないパラメーター リスト、または重複するパラメーターを含むリストを受け入れます。ただし、既存の有効なコードについては、パラメーターを含むリストを任意の順序で解析します。

最終的なパーサーは次のとおりです。

quotedString.setParseAction(removeQuotes)
list_of_names = delimitedList(quotedString)
boolean_value = oneOf("True False").setParseAction(lambda t: t[0]=='True')

def named_parameter(label, paramtype):
    expr = Literal(label) + Suppress('=') + paramtype(label)
    return expr

person_start = Literal("person(").suppress()
first = named_parameter("firstname", quotedString)
last = named_parameter("lastname", quotedString)
dob = named_parameter("dob", quotedString)
hobbies = named_parameter("hobbies", Suppress("[") + list_of_names + Suppress("]"))
is_minor = named_parameter("is_minor", boolean_value)
person_end = Suppress(")")
comma = Literal(",").suppress()

parameter = first | last | dob | hobbies | is_minor
my_data = person_start + delimitedList(parameter) + person_end
于 2013-11-10T07:29:28.907 に答える
1

リテラルにあまり依存しないように、本当に分割する必要があります...したがって、「X = Y」などのトークンを探して、より一般的にします...

あるいは、別のオプション (Python 関数呼び出しを解析しようとしているように見えるため) は、次のようなものです。

data = 'person(firstame="bob", lastname="stewart", dob="2010-0206", hobbies=["reading, singing", "drawing"], is_minor=True)'

import ast
d = {}
for kw in ast.parse(data).body[0].value.keywords:
    if isinstance(kw.value, ast.List):
        d[kw.arg] = [el.s for el in kw.value.elts]
    else:
        d[kw.arg] = getattr(kw.value, {ast.Name: 'id', ast.Str: 's'}[type(kw.value)])

# {'dob': '2010-0206', 'lastname': 'stewart', 'is_minor': 'True', 'firstame': 'bob', 'hobbies': ['reading, singing', 'drawing']} 
于 2013-11-10T01:31:04.287 に答える