勇者だって最初はひのきの棒とナベのふた

書きたい技術。時々SFの読書感想も。

getattr()とsetattr()

はじめに

masamoriです。
今回もPythonのお話。
「実装で困ったらソースコードを見よ」. これはプログラマーの不文律です。使ってるメソッドでよくわからんことがあったらドキュメント読んだり、場合によってはソースコード読むのは鉄則です。これをしないで先輩に質問すると、優しい先輩は「ソースコード確認した?」としか返すことのできない村人のモブキャラへとたちまち変わってしまいます。最近は「GPTに聞いた?」と変化しつつあります。人間というソースコードが変更したようです。
さて、何かしらのソースコードを読むとほぼ確実にgetattr()とsetattr()が出てきます。(ような気がします)
これ、正直分かりにくいんです。というよりスラスラコードが読めないんです。え、僕の能力?ソノハッソウハナカッタワ。

というわけで、今回は自分なりにgetattr()とsetattr()を調べてみました。

説明

Python の組み込み関数で、オブジェクトの属性を動的に操作する際に使用される。

思考と実験

これらの関数は、属性名が変数として動的に決定される場合や、リフレクションを使用してオブジェクトの状態を操作する場合に非常に便利らしい。
はいはいなるほどね、まぁと言っても使い所がよく分かりません。
オブジェクトに対して属性を動的に割り当てたり、取り出しする時っていつなんだ?

というわけでchatGPTに色々聞いてみてユースケースとコードを書いてみました

動的属性アクセスと属性の変更

class MyClass:
    def __init__(self):
        self.name = "Ex"
        self.value = 42
         
obj = MyClass()
         
# ユーザー入力として動的な属性名を取得
attribute_name = input("Enter the attribute name you want to access (name/value): ")
   
# 動的に属性を取得
try:
    attribute_value = getattr(obj, attribute_name)
        print(f"The value of '{attribute_name}' is: {attribute_value}")
    except AttributeError:
        print(f"Attribute '{attribute_name}' does not exist.")
       
# 動的に属性を設定
new_value = input(f"Enter the new value for '{attribute_name}': ")
setattr(obj, attribute_name, new_value)
   
# 設定後の値を確認
attribute_value = getattr(obj, attribute_name)
print(f"The new value of '{attribute_name}' is: {attribute_value}")

まぁ、これはありそう。ユーザーからの入力に合わせて属性を取得して表示したり、取得した属性を変えたりする場合は普通にありそう。

ただ、これってデータクラスとかで代用できないのでしょうか?例えばユーザー名とか年齢とかは予めデータクラスで定義しておいて、その属性を変えた方が可読性が上がりそうです。
特に、属性が多い場合。
うーん、データクラスは後発なので昔はこういうやり方だったのかなぁという気持ち。

リフレクションを使用してオブジェクトのすべての属性を操作

class MyClass:
    def __init__(self):
        self.attr1 = "value1"
        self.attr2 = "value2"
        self.attr3 = "value3"
    
obj = MyClass()
   
attributes = dir(obj)
for attribute in attributes:
    if not attribute.startwith('__'):
    value = getattr(obj, atribute)
    print(f"Attribute '{attribute}' has value '{value}'")
        
# 特定の属性を変更
setattr(obj, 'attr2', 'new_value2')
   
# 変更後の属性を確認
for attribute in attributes:
      if not attribute.startswith('__'):
          value = getattr(obj, attribute)
          print(f"Attribute '{attribute}' has value '{value}'")

これいつ使うんだ?
リフレクションの時に使うか・・?
例えばある特定の属性がそのオブジェクトにあるかどうかをチェックして、あったらその属性をsetattrで変える。それをgetattrして確認。みたいな使い方でしょうか? 誰か「これぞ!」というコードがあったらください。

設定と取得のカプセル化

class Config:
    def __init__(self):
        self.settings = {}
   
    def set(self, name, value):
         setattr(self, name, value)
   
    def get(self, name, default=None):
         return getattr(self, name, default)
   
config = Config()
   
config.set('database_url', 'http://localhost:5432/mydb')
config.set('debug', True)
   
print(config.get('database_url'))  # http://localhost:5432/mydb
print(config.get('debug'))         # True
print(config.get('missing_attr', 'default_value'))  # default_value

これは全然ありそう。
例えばユーザーごとに設定を変えたい場合や、環境ごとに設定を変えたい場合に便利かもしれません。
また、setとgetでしかConfigにアクセス出来ないようになっているので、アクセス制御が統一できるのも利点の一つでしょう。さらに、getしたときに設定項目がない場合、デフォルトで値を返すことが出来るのでそれも便利です。

終わりに

setattr(), getattr(), hasattr(),この辺はオープンソースのコード読む時必ずと言っていいほど出てくるので、慣れた方が良いと思います。ただ、自分で流れるようにこのメソッドを使うには練習が必要です。
属性が簡単に取得できたり変えられたり出来るから非常に柔軟性が高く、便利なライブラリだなぁと思いました。
しかし一方で、あんまり使い回すとコードの可読性が下がりそうなので、使いすぎは良くなさそうです。