3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

pythonのsetattrを使って、CSVヘッダーをModelクラスの属性名として動的に追加する

Posted at

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
3
5
0

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
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?