4

xml.etree.ElementTreePython のモジュールのiterparse()メソッドを使用して、巨大な (数ギガバイト) XML ファイルを読み込んでいます。問題は、XML ファイルのテキストの一部に時折 Unicode エラー (または少なくとも Python 3 が Unicode エラーと見なすもの) があることです。私のループは次のように設定されています:

import xml.etree.ElementTree as etree

def foo():
    # ...
    f = open(filename, encoding='utf-8')
    xmlit = iter(etree.iterparse(f, events=('start', 'end')))
    (event, root) = next(xmlit)
    for (event, elem) in xmlit: # line 26
        if event != 'end':
            continue
        if elem.tag == 'foo':
            do_something()
            root.clear()
        elif elem.tag == 'bar':
            do_something_else()
            root.clear()
    # ...

Unicode エラーのある要素が検出されると、次のトレースバックでエラーが発生します。

Traceback (most recent call last):
  File "<path to above file>", line 26, in foo
    for (event, elem) in xmlit:
  File "C:\Python32\lib\xml\etree\ElementTree.py", line 1314, in __next__
    self._parser.feed(data)
  File "C:\Python32\lib\xml\etree\ElementTree.py", line 1668, in feed
    self._parser.Parse(data, 0)
UnicodeEncodeError: 'utf-8' codec can't encode character '\ud800' in position 16383: surrogates not allowed

エラーはループの繰り返しの間に発生するため、ブロックforをラップできる唯一の場所はループの外側です。これは、次の XML 要素に進むことができないことを意味します。tryfor

解決策の優先事項は次のとおりです。

  1. 例外を発生させる代わりに、必ずしも有効ではない Unicode 文字列を要素のテキストとして受け取ります。
  2. 無効な文字が置換または削除された有効な Unicode 文字列を受け取ります。
  3. 無効な文字を含む要素をスキップして、次の要素に進みます。

ElementTree自分でコードを変更せずに、これらのソリューションを実装するにはどうすればよいですか?

4

1 に答える 1

4

まず、ElementTree に関するすべてのことは、おそらくここでは無関係です。によって返されたファイルを列挙してみるf = open(filename, encoding='utf-8')と、おそらく同じエラーが発生します。

その場合、解決策は、ドキュメントで説明されているように、デフォルトのエンコーディング エラー ハンドラをオーバーライドすることです。

errors は、エンコード エラーとデコード エラーの処理方法を指定するオプションの文字列です。これは、バイナリ モードでは使用できません。エンコーディング エラーがある場合に ValueError 例外を発生させるには 'strict' を渡します (デフォルトの None は同じ効果があります)、または 'ignore' を渡してエラーを無視します。(エンコーディング エラーを無視すると、データが失われる可能性があることに注意してください。) 'replace' を指定すると、不正なデータがある場所に置換マーカー ('?' など) が挿入されます。書き込み時には、'xmlcharrefreplace' (適切な XML 文字参照に置き換える) または 'backslashreplace' (バックスラッシュ付きのエスケープ シーケンスに置き換える) を使用できます。codecs.register_error() で登録されたその他のエラー処理名も有効です。

したがって、これを行うことができます:

f = open(filename, encoding='utf-8', errors='replace')

これは 2 番目の優先事項に適合します。無効な文字は に置き換えられ'?'ます。

「必ずしも有効ではない Unicode 文字列」を表す方法がないため、最優先事項に適合する方法はありません。Unicode 文字列は、定義上、一連の Unicode コード ポイントであり、Python はそのようにstr型を扱います。無効な UTF-8 を文字列に変換したい場合は、文字列に変換する方法を指定する必要がありますerrors

別の方法として、ファイルをバイナリ モードで開き、UTF-8 をbytesUnicodestrオブジェクトに変換する代わりにオブジェクトとしてそのままにしておくこともできますが、その場合は、オブジェクトを操作する API しか使用できませんbytes。( の実装では実際にこれができると思いますが、組み込みのlxml実装ではElementTreeできませんが、引用しないでください。)しかし、それを行ったとしても、それほど遠くまでは行きません.コード自体は無効な UTF-8 を解釈しようとします。次に、エラーに対して何をしたいのかを知る必要があります。それは通常、より下にあるため、指定するのが難しくなります

最後のポイント:

エラーは for ループの繰り返しの間に発生するため、try ブロックをラップできる唯一の場所は for ループの外側です。つまり、次の XML 要素に進むことができません。

for実際には、ループを使用する必要はありません。明示的な呼び出しでwhileループに変換できます。nextこれを行う必要がある場合は、通常、何か間違ったことをしている兆候ですが、壊れたライブラリを扱っている兆候である場合もあり、それが利用可能な唯一の回避策です。

これ:

for (event, elem) in xmlit: # line 26
    doStuffWith(event, elem)

実質的には次のものと同等です:

while True:
    try:
        event, elem = next(xmlit)
    except StopIteration:
        break
    doStuffWith(event, elem)

そして今、a を追加する明らかな場所がありtryますが、実際には必要ありません。except既存の に別のものを取り付けることができますtry

問題は、ここで何をするかです。例外をスローした後、反復子が続行できるという保証はありません。実際、イテレータを作成する最も簡単な方法のすべてがそうできるわけではありません。この場合、それが正しいかどうかを自分でテストできます。

これが必要なまれなケースであり、実際に役立つ場合は、おそらくそれをまとめたいと思うでしょう。このようなもの:

def skip_exceptions(it):
    while True:
      try:
          yield next(it)
      except StopIteration:
          raise
      except Exception as e:
          logging.info('Skipping iteration because of exception {}'.format(e))        

次に、次のようにします。

for (event, elem) in skip_exceptions(xmlit):
    doStuffWith(event, elem)
于 2013-01-04T20:08:42.727 に答える