これはおそらく単純な問題なので、誰かが私の間違いを指摘したり、これが可能かどうかを簡単に指摘したりすることができれば幸いです。
プロパティとして複数のオブジェクトを持つオブジェクトがあります。これらのオブジェクトのプロパティを次のように動的に設定できるようにしたいと思います。
class Person(object):
def __init__(self):
self.pet = Pet()
self.residence = Residence()
class Pet(object):
def __init__(self,name='Fido',species='Dog'):
self.name = name
self.species = species
class Residence(object):
def __init__(self,type='House',sqft=None):
self.type = type
self.sqft=sqft
if __name__=='__main__':
p=Person()
setattr(p,'pet.name','Sparky')
setattr(p,'residence.type','Apartment')
print p.__dict__
出力は次のとおりです。
{'pet':<main。Petオブジェクトat 0x10c5ec050>、 'residence':<main。Residence object at 0x10c5ec0d0>、 'pet.name': 'Sparky' 、 'residence.type': 'アパート'}
ご覧のとおり、個人のペットオブジェクトにname属性を設定するのではなく、新しい属性「pet.name」が作成されます。
Person.petをsetattrに指定することはできません。異なる子オブジェクトが同じメソッドによって設定されるためです。このメソッドは、テキストを解析し、関連するキーが見つかった場合にオブジェクト属性を入力します。
これを達成するための簡単な方法がありますか?
またはおそらく、文字列を解析し、必要な子オブジェクトが見つかるまでgetattrを複数回呼び出してから、見つかったオブジェクトでsetattrを呼び出す再帰関数を作成する必要がありますか?
ありがとうございました!
import functools
def rsetattr(obj, attr, val):
pre, _, post = attr.rpartition('.')
return setattr(rgetattr(obj, pre) if pre else obj, post, val)
# using wonder's beautiful simplification: https://stackoverflow.com/questions/31174295/getattr-and-setattr-on-nested-objects/31174427?noredirect=1#comment86638618_31174427
def rgetattr(obj, attr, *args):
def _getattr(obj, attr):
return getattr(obj, attr, *args)
return functools.reduce(_getattr, [obj] + attr.split('.'))
rgetattr
とrsetattr
は、getattr
とsetattr
のドロップイン置換であり、ドット付きattr
文字列も処理できます。
import functools
class Person(object):
def __init__(self):
self.pet = Pet()
self.residence = Residence()
class Pet(object):
def __init__(self,name='Fido',species='Dog'):
self.name = name
self.species = species
class Residence(object):
def __init__(self,type='House',sqft=None):
self.type = type
self.sqft=sqft
def rsetattr(obj, attr, val):
pre, _, post = attr.rpartition('.')
return setattr(rgetattr(obj, pre) if pre else obj, post, val)
def rgetattr(obj, attr, *args):
def _getattr(obj, attr):
return getattr(obj, attr, *args)
return functools.reduce(_getattr, [obj] + attr.split('.'))
if __name__=='__main__':
p = Person()
print(rgetattr(p, 'pet.favorite.color', 'calico'))
# 'calico'
try:
# Without a default argument, `rgetattr`, like `getattr`, raises
# AttributeError when the dotted attribute is missing
print(rgetattr(p, 'pet.favorite.color'))
except AttributeError as err:
print(err)
# 'Pet' object has no attribute 'favorite'
rsetattr(p, 'pet.name', 'Sparky')
rsetattr(p, 'residence.type', 'Apartment')
print(p.__dict__)
print(p.pet.name)
# Sparky
print(p.residence.type)
# Apartment
1つの親と1つの子の場合:
if __name__=='__main__':
p = Person()
parent, child = 'pet.name'.split('.')
setattr(getattr(p, parent), child, 'Sparky')
parent, child = 'residence.type'.split('.')
setattr(getattr(p, parent), child, 'Sparky')
print p.__dict__
これは、この特定のユースケースの他の回答よりも簡単です。
magicattr と呼ばれるubntuの答えに基づいた単純なバージョンを作成しました。これは、astを解析およびウォーキングすることにより、attrs、リスト、およびdictでも機能します。
たとえば、このクラスでは:
class Person:
settings = {
'autosave': True,
'style': {
'height': 30,
'width': 200
},
'themes': ['light', 'dark']
}
def __init__(self, name, age, friends):
self.name = name
self.age = age
self.friends = friends
bob = Person(name="Bob", age=31, friends=[])
jill = Person(name="Jill", age=29, friends=[bob])
jack = Person(name="Jack", age=28, friends=[bob, jill])
あなたはこれを行うことができます
# Nothing new
assert magicattr.get(bob, 'age') == 31
# Lists
assert magicattr.get(jill, 'friends[0].name') == 'Bob'
assert magicattr.get(jack, 'friends[-1].age') == 29
# Dict lookups
assert magicattr.get(jack, 'settings["style"]["width"]') == 200
# Combination of lookups
assert magicattr.get(jack, 'settings["themes"][-2]') == 'light'
assert magicattr.get(jack, 'friends[-1].settings["themes"][1]') == 'dark'
# Setattr
magicattr.set(bob, 'settings["style"]["width"]', 400)
assert magicattr.get(bob, 'settings["style"]["width"]') == 400
# Nested objects
magicattr.set(bob, 'friends', [jack, jill])
assert magicattr.get(jack, 'friends[0].friends[0]') == jack
magicattr.set(jill, 'friends[0].age', 32)
assert bob.age == 32
また、evalを使用したり、Assign/Callノードを許可したりしないため、関数を呼び出したり値を割り当てたりすることもできません。
with pytest.raises(ValueError) as e:
magicattr.get(bob, 'friends = [1,1]')
# Nice try, function calls are not allowed
with pytest.raises(ValueError):
magicattr.get(bob, 'friends.pop(0)')
unutbuの答え( https://stackoverflow.com/a/31174427/2683842 )には「バグ」があります。 getattr()
が失敗し、default
に置き換えられた後、getattr
でdefault
の呼び出しを続けます。
例:rgetattr(object(), "nothing.imag", 1)
は私の意見では_1
_に等しいはずですが、_0
_を返します:
getattr(object(), 'nothing', 1)
== 1。getattr(1, 'imag', 1)
== 0(1は実数であり、複素数成分がないため)。最初に欠落している属性でdefault
を返すようにrgetattrを変更しました:
_import functools
DELIMITER = "."
def rgetattr(obj, path: str, *default):
"""
:param obj: Object
:param path: 'attr1.attr2.etc'
:param default: Optional default value, at any point in the path
:return: obj.attr1.attr2.etc
"""
attrs = path.split(DELIMITER)
try:
return functools.reduce(getattr, attrs, obj)
except AttributeError:
if default:
return default[0]
raise
_
わかりましたので、質問を入力している間、私はこれを行う方法のアイデアを持っていて、それはうまくいくようです。ここに私が思いついたものがあります:
def set_attribute(obj, path_string, new_value):
parts = path_string.split('.')
final_attribute_index = len(parts)-1
current_attribute = obj
i = 0
for part in parts:
new_attr = getattr(current_attribute, part, None)
if current_attribute is None:
print 'Error %s not found in %s' % (part, current_attribute)
break
if i == final_attribute_index:
setattr(current_attribute, part, new_value)
current_attribute = new_attr
i+=1
def get_attribute(obj, path_string):
parts = path_string.split('.')
final_attribute_index = len(parts)-1
current_attribute = obj
i = 0
for part in parts:
new_attr = getattr(current_attribute, part, None)
if current_attribute is None:
print 'Error %s not found in %s' % (part, current_attribute)
return None
if i == final_attribute_index:
return getattr(current_attribute, part)
current_attribute = new_attr
i += 1
私はこれで私の質問が解決したと思いますが、これを行うためのより良い方法があるかどうか私はまだ興味がありますか?
これはOOPとpythonでかなり一般的なものでなければならないので、gatattrとsetattrはこれをネイティブにサポートしていないことに驚いています。
そして、jimbo1qazの答えに基づいたわかりやすい3ライナーは、極限まで削減されました。
def rgetattr(obj, path, default):
try:
return functools.reduce(getattr, path.split(), obj)
except AttributeError:
return default
使用法:
>>> class O(object):
... pass
... o = O()
... o.first = O()
... o.first.second = O()
... o.first.second.third = 42
... rgetattr(o, 'first second third', None)
42
「スペース」はこのユースケースの典型的な区切り文字ではないことに注意してください。
再帰関数が大好きです
def rgetattr(obj,attr):
_this_func = rgetattr
sp = attr.split('.',1)
if len(sp)==1:
l,r = sp[0],''
else:
l,r = sp
obj = getattr(obj,l)
if r:
obj = _this_func(obj,r)
return obj