📌

【Python】getattr,setattrの改良

2023/01/03に公開約2,100字

結論

def getattr_(object,attrs):
    v = object #objectによってはcopy.copy(),copy.deepcopy()した方が良い
    for a in attrs.split('.'):
        v = getattr(v,a)
    return v

setattrを改良したい場合はこちら

def setattr_(object,attrs,value):
    v = object #objectによってはcopy.copy(),copy.deepcopy()した方が良い
    split_attr = attrs.split('.')
    for a in split_attr[:-1]:
        v = getattr(v,a)
    setattr(v,split_attr[-1],value)

はじめに

getattr,setattrは(主観ですが)Pythonにおけるオブジェクト志向でのプログラミングで欠かせない要素です。

ご存知ない方は以下記事をご覧ください。

https://zenn.dev/sergicalsix/articles/931ca295c36281

getattrの改良案

getattrについて考えるために以下のプログラムを用意します。

class A():
    def __init__(self, x = 1):
        self.x = x
class B():
    def __init__(self, y = 1):
        self.a = A()
	self.y = 1
z = B()

このように定義されたインスタンスzz.a.x = 1及びz.y = 1という二つの値にアクセスできます。

ここでgetattrを使って値にアクセスすることを考えます。
以下3つのケースを考えます。

getattr(z,'y') #ケース(1)正しい
getattr(z,'x') #ケース(2)正しくない
getattr(z,'a.x') #ケース(3)正しくない
getattr(z.a,'x') #ケース(4)正しい

ここでケース(2)(3)は間違いです。

(2)はzxを直接保持していないから論外としても、ケース(3)はお気持ち的には通って欲しいです。通らない理由は、a.xがただの文字列として処理されているからです。

ケース(4)は正しいですが、今回はプログラムでz.aとハードコーディングできない場合を考えます。

今回の問題はa.xが文字列として処理されてしまうことで、getattr(z,'a')->getattr(a,'x')というように順番にgetattrしていけば問題が解決しそうです。

つまりa.xという文字列に対してsplit('.')で分割して順番にgetattrしていけばいいので、実装は以下になります。

def getattr_(object,attrs):
    v = object #objectによってはcopy.copy(),copy.deepcopy()した方が良い
    for a in attrs.split('.'):
        v = getattr(v,a)
    return v

以下では同じことをsetattrについても考えます。

setattrの改良

setattrは挙動が少し厄介です。

というのも以下がエラーを吐かずに実行できてしまうからです。

setattr(z,'a.x',5) #エラーが出ない

これが相当曲者で、実際にはzがアクセスできるaのメンバーxの値を書き換えることはできておらず、zは新たにa.xというメンバーを保持します。

これの中身は以下のようにして見ることが出来ます。

In: print(z.a.x, getattr(z,'a.x'))
1,5

本来はsetattr(z,'a.x',5)aのメンバーxの値を書き換えたかったので、以下のように改良します。

def setattr_(object,attrs,value):
    v = object #objectによってはcopy.copy(),copy.deepcopy()した方が良い
    split_attr = attrs.split('.')
    for a in split_attr[:-1]:
        v = getattr(v,a)
    setattr(v,split_attr[-1],value)

このようにするとsetattr_(z,'a.x',5)aのメンバーxの値を5に書き換えることができます。

Discussion

ログインするとコメントできます