pythonにはcsvを読み込む便利なモジュールがありますよね。あれを使ってDBのテストレコードを挿入を試してみました。その際にModelにCVSの各カラム値を、下記のように属性名を記述して代入をしていると、カラム値・Modelクラスの変更に伴いソースコードの変更が発生します。これをなくすためのリファクタリングをしていきます。
setattrのメリット、使い道はリフレクトでも分かるかもしれませんが、このサンプルのほうが分かりやすいかと思います。
実行環境はPython 3.4.3を想定しています。
import csv
class Model():
pass
if __name__ == '__main__':
models = []
with open('person.csv', 'r') as f:
reader = csv.reader(f)
# 1行だけ読み込む(ヘッダー)
Model._columns = next(reader)
for row in reader:
model = Model()
#動的にmodelに属性をセット
for i in range(len(row)):
model.coulmn1 = row[0]
model.coulmn2 = row[1]
models.append(model)
属性のセットにsetattrを使う
先に今回使うcsvファイルを掲示しておきます。
id,name,age
1,Taro,19
2,Ziro,18
3,Mikio,40
4,Mineko,38
pythonにはsetattrという組み込み関数があります。setattr(オブジェクト,属性名,値)
として使います。これの何がいいと言うと、属性名を変数で指定することができます。obj.var1
にするとobjの中にvar1がないか探してしまいます。もしvar1が変数で展開して欲しい場合はsetattr()
を使いましょう。
import csv
class Model():
pass
if __name__ == '__main__':
models = []
with open('person.csv', 'r') as f:
reader = csv.reader(f)
# 1行だけ読み込む(ヘッダー)
columns = next(reader)
for row in reader:
model = Model()
#動的にmodelに属性をセット
for i in range(len(row)):
setattr(model, columns[i], row[i])
models.append(model)
for model in models:
print(model)
# =>
# <__main__.Model object at 0x1053169b0>
# <__main__.Model object at 0x105316a58>
# <__main__.Model object at 0x105316b00>
# <__main__.Model object at 0x105316ba8>
属性一覧の順番を維持する
今のままだとオブジェクトの属性が出力されないので、dirを使わないでもデフォルトで出力されるようにします。pythonではprint文で出力する際に__str__
という特殊メソッドが実行されます。このメソッドで表示したい文字列を返してあげれば良いです。
接頭辞が_(アンダーバー)ではないものは表示から除くようにしています。これによりModelに内部処理のためのメソッドなどを定義したい場合は、_(アンダーバー)をつけてあげれば良いです。
import csv
class Model():
# 要素の長さと比較するときに、属性数に含まれてしまうため
# アンダーバーをつける
_columns = []
def __init__(self):
if len(self._columns) == 0:
raise Exeption("Please set the columns of %s" % self)
def __str__(self):
attribute_str = ""
# 接頭辞が_のものを除外
attributes = list(filter(
lambda attribute_name: not(attribute_name[0].startswith("_")),
dir(self)))
if len(attributes) == len(self._columns):
# selfにcolumnsはないため、クラス変数を参照
for attribute in self._columns:
attribute_str += "%s = %s \n" % (attribute,
getattr(self, attribute))
else:
raise Exception("Incorrect the number of %s.columns" % self)
return attribute_str
if __name__ == '__main__':
models = []
with open('person.csv', 'r') as f:
reader = csv.reader(f)
# 1行だけ読み込む(ヘッダー)
Model._columns = next(reader)
for row in reader:
model = Model()
#動的にmodelに属性をセット
for i in range(len(row)):
setattr(model, Model._columns[i], row[i])
models.append(model)
for model in models:
print(model)
# =>
# id = 1
# name = Taro
# age = 19
#
# id = 2
# name = Ziro
# age = 18
#
# id = 3
# name = Mikio
# age = 40
#
# id = 4
# name = Mineko
# age = 38