LoginSignup
4
3

More than 1 year has passed since last update.

文字列中の複数の文字列を同時に置換

Last updated at Posted at 2022-11-22

文字列中で置換したい文字列が複数あるとき、.replace() メソッドを連ねて書くことが多いですが問題もあります。先にreplaceされて出てきた文字列があとのreplaceで対象になってしまう現象ですね。
これがあるので、例えば二つのワードを相互に入れ替えるというのがそう簡単ではありません。

"YES NO YES NO".replace("YES", "NO").replace("NO", "YES")

==> "YES YES YES YES"
# 相互入替になってくれていない!

str.translateというメソッドは同時置換ができるので目標にやや近いのですが、置換対象が文字列でなくて文字なので、2文字以上の文字列を対象にできず、利用シーンは限られます。

カッとなって同時置換してくれるモジュールを作ったので、ご入り用の際はご活用ください。Python2/3両対応で作ってあります。

import re

def multi_replace(string, mapping):
    """
    文字列中の複数の文字列やパターンを同時に置換します。
    .replace を連ねる場合と違い、ある置換結果がさらに別の置換を受けてしまうことはありません。
    よって二つのワードを相互に入れ替えることもできます。

    mapping: 検索対象をキー、その置換文字列を値とする辞書。
        キーとしてstrまたはreパターンオブジェクトを指定できる。
        キーがreパターンオブジェクトの場合は、置換文字列中でグループ参照 '\\1', '\\2',... が有効
    """
    catch_all_pattern = '|'.join(map(to_pattern, mapping))
    replacer = make_replacer(mapping)

    return re.subn(catch_all_pattern, replacer, string)[0]


_PatternType = type(re.compile(''))  # workaround for Python which does't have typing module

def to_pattern(key):
    if isinstance(key, str):
        return '(' + re.escape(key) + ')'
    elif isinstance(key, _PatternType):
        return '(' + key.pattern + ')'
    else:
        raise ValueError(expr(key) + " is not a str, neither re.compile.")


def make_replacer(mapping):

    def _replacer(match):
        src = match.group(0)
        for key, val in mapping.items():
            if src == key:
                return val
            elif isinstance(key, _PatternType) and re.match("(?:" + key.pattern + r")\Z", src):  # workaround for Python which doesn't have re.fullmatch
                return key.sub(val, src)

    return _replacer
4
3
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3