Python 指定文字列を複数行に渡って置換する方法

'20/08/13更新:見づらかったので記事構成を編集しました。
 本コードの仕様について説明します。下図左にある「@TARGET_*@」のように@で囲まれた複数箇所を下図右のように置換する雛形コードを載せました。複数行に渡って置換している元データは、別ファイルからpandasを用いて抽出後、加工して置換します。

f:id:HK29:20190511233027p:plain

 具体例を示します。下図4つのファイルを作成します。

f:id:HK29:20190511233950p:plain

▼ファイル①「abc.txt」
置換対象のファイルです。@で囲まれた箇所を置換します。

aaa

hhhhhhhhhhhhhhhhhh


22222222

###################################
@TARGET_2@
###################################


@TARGET_3@
@TARGET_1@

end

▼ファイル②「dataA.csv
置換するデータです。ここから指定データをpandasで抽出します。

No,Property,Material
1,IS1,Cu
2,GF1_2,Ag
3,NT1_2,Au
4,GF1_3,Ag
5,GF1_1,Ag
6,NT1_1,Au
7,GF1_5,Ag
8,GF1_4,Ag
9,NT1_4,Au
10,IS2,Cu
11,GF2_2,Ag
12,NT2_2,Au
13,GF2_3,Ag
14,NT2_3,Au
15,GF2_1,Ag
16,NT2_1,Au
17,GF2_5,Ag
18,NT2_5,Au
19,GF2_4,Ag
20,NT2_4,Au
21,IS3,Cu

▼ファイル③「dataB.csv
置換するデータです。ここから指定データをpandasで抽出します。

name,x_min,x_max,y_min,y_max,z_min,z_max
A1,0,0.3,0.15,0.15,-0.1,0.1
A2,0,0.3,0.15,0.15,0.2,0.4
A3,0,0.3,0.15,0.15,0.5,0.7
B1,0,0.3,-0.15,-0.15,-0.1,0.1
B2,0,0.3,-0.15,-0.15,0.2,0.4
B3,0,0.3,-0.15,-0.15,0.5,0.7

▼ファイル④「replace.py」
本体プログラムです。例えば、コマンドプロンプトでカレントディレクトリへ移動して「python replace.py」と実行すれば、冒頭の図のように置換した結果ファイルを得られます。

■本プログラム 

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os, sys, shutil
import pandas as pd

def myComment():
    buf=[] # 空のリスト(配列)を作成
    buf.append('fruits' + '\n') # appendメソッドでリストに要素を追加する
    buf.append('  orange' + '\n')
    buf.append('  apple' + '\n')
    buf.append('  grape' + '\n')
    
    return buf


class Extract_dataA:
    def __init__(self, data_file): # インスタンス生成時に呼び出される。コンストラクタ
        self.df = pd.read_csv(data_file, header=0) # pandas data frame' # header=0は一行目のことでdefaultで書かなくても可
        print('self.df -> ' + str(self.df))

    def select_columndata(self, select_column, extract_name): # 指定列に指定した文字列がある行データを全て抽出
        select_df = self.df[self.df[select_column]==extract_name]

        return select_df
    
    def sort_columndata(self, DF, sort_column): # 指定列を基準にソート(並び替え)する
        sort_df = DF.sort_values(sort_column, ascending=True)

        return sort_df
    
    def extra_columndata(self, DF, extract_name, extract_column): # 指定列のデータを下記フォーマットへ代入してリスト化
        columnlist = DF[extract_column].values.tolist()
        print('columnlist -> ' + str(columnlist))
        buf=[]
        buf.append('dataset '+ str(dict[extract_name]) + '=[')
        for i in range(len(columnlist)):
            if (i < len(columnlist)-1):
                buf.append('\"' + extract_column + str(columnlist[i])+'\",')
            else:
                buf.append('\"' + extract_column + str(columnlist[i])+'\"')
        buf.append(']' + '\n')
        
        return buf


class Extract_dataB:
    def __init__(self, data_file): # インスタンス生成時に呼び出される。コンストラクタ
        self.df = pd.read_csv(data_file, header=0, index_col=0) # pandas data frame'
        print('self.df -> ' + str(self.df))
        self.indexlist = self.df.index.values.tolist() # インデックス名をリスト化
        print(self.indexlist)        
        
    def data_set(self):
        buf=[]
        for i in range(len(self.indexlist)):
            tmplist=[]
            tmplist=self.df.loc[str(self.indexlist[i])].values.tolist() # 各インデックスの行データをリスト化
            buf.append('func(name=' +str(self.indexlist[i]) + ',\n')
            buf.append('     x_min='+str(tmplist[0])+ ', x_max=' +str(tmplist[1])+ ',\n')
            buf.append('     y_min='+str(tmplist[2])+ ', y_max=' +str(tmplist[3])+ ',\n')
            buf.append('     z_min='+str(tmplist[4])+ ', z_max=' +str(tmplist[5])+ ')\n')
        
        return buf
        

def create_file(replace_set, replace_list):
    tmp_list=[]
    flag=0
    with open(replace_set[0], "r") as f1:
        k=0
        for row in f1:
            if row.find(replace_set[2]) != -1:
                flag = k
                print('flag -> ' + str(flag))
                tmp_list.append(replace_list)
                print(tmp_list[flag])
            else:
                tmp_list.append(row)
            k=k+1
    if replace_set[0]==replace_set[1]: # 入力ファイル名と出力ファイル名を同じに指定した場合。上書きエラーを回避
        os.remove(replace_set[0])
            
    with open(replace_set[1], "w") as f2:
        for i in range(len(tmp_list)):
            if i != flag:
                f2.write(str(tmp_list[i])) # 文字列を書く場合
            else:
                f2.writelines(tmp_list[i]) # リストの各要素を全て書き出す場合。ちなみに、要素毎に改行する場合は各要素に改行コードを入れる必要あり。


if __name__ == "__main__":

    #               inputfile,  outfile,    target_sentence
    replace_set1 = ('abc.txt', 'abc2.txt', '@TARGET_1@')
    replace_set2 = ('abc2.txt', 'abc3.txt', '@TARGET_2@')
    replace_set3 = ('abc3.txt', 'abc4.txt', '@TARGET_3@')

    replace_list1 = myComment()
    
    replace_list2 = []
    data_file = 'dataA.csv'
    select_column = 'Material'
    extract_name = ['Au','Ag','Cu']
    dict={"Au":'gold', "Ag":'silver', "Cu":'bronze'}
    extract_column = 'No' 
    sort_column = 'Property'
    for i in range(len(extract_name)):
        myInstanceA = Extract_dataA(data_file) # インスタンスの生成
        mydf1 = myInstanceA.select_columndata(select_column, extract_name[i]) # メソッドの実行
        print(mydf1)
        mydf2 = myInstanceA.sort_columndata(mydf1, sort_column)  # メソッドの実行
        print(mydf2)
        mylist = myInstanceA.extra_columndata(mydf2, extract_name[i], extract_column)  # メソッドの実行
        print(mylist)
        replace_list2.extend(mylist) # リストの最後にリストを追加。多重配列(ジャグ配列)
    print('replace_list2-----------------')
    print(replace_list2)
    
    data_file = 'dataB.csv'
    myInstanceB = Extract_dataB(data_file) # インスタンスの生成
    replace_list3 = myInstanceB.data_set()
    
    create_file(replace_set1, replace_list1)
    create_file(replace_set2, replace_list2)
    create_file(replace_set3, replace_list3)
    

本コードに関する特記事項は下記二つです。

1. 置換するためのデータファイルを読み込むために、クラスを二つ用意した。

インデックス(行名)がないcsvファイル等を読み込む用にはクラス「Extract_dataA」。一方のクラス「Extract_dataB」はインデックスを一番左列に指定してcsv等のファイルを読み込む用です。それぞれ、その下に関数(クラスなのでメソッド)を作成していく仕様で、汎用性を考慮したためです。

2. 置換元と置換後のファイル名をinputfile, outputfileで指定できる。

もし、両者を同名で指定すれば、ファイル自体も置換した結果になります。例えば、本コード中の「abc2.txt」「abc3.txt」「abc4.txt」を「abc.txt」にすれば可能です。

以上

<広告>