【Python】getattr,setattrの改良
結論
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におけるオブジェクト志向でのプログラミングで欠かせない要素です。
ご存知ない方は以下記事をご覧ください。
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()
このように定義されたインスタンスz
はz.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)はz
はx
を直接保持していないから論外としても、ケース(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