getattr et setattr sur des objets imbriqués?
c'est probablement un problème simple donc, heureusement nous, ses facile pour quelqu'un de mon erreur ou si c'est encore possible.
j'ai un objet qui a plusieurs objets comme propriétés. Je veux être en mesure de définir dynamiquement les propriétés de ces objets comme:
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__
la sortie est:
{'animal de compagnie': < principal .Pet objet au 0x10c5ec050>, de résidence: < principal .Résidence de l'objet à 0x10c5ec0d0>, 'animal de compagnie.name': 'Simple', 'résidence.type': 'Appartement'}
comme vous pouvez le voir, plutôt que d'avoir le nom de l'attribut placé sur l'objet pet de la personne, un nouvel attribut" pet.nom " est créé.
Je ne peux pas spécifier la personne.pet to setattr parce que différents objets-enfants seront définis par la même méthode, qui analyse du texte et remplit les attributs de l'objet si/quand une clé pertinente est trouvée.
y a-t-il un moyen facile/construit pour accomplir ceci?
ou peut-être ai-je besoin d'écrire une fonction récursive pour analyser la chaîne et appeler getattr plusieurs fois jusqu'à ce que l'enfant-objet nécessaire soit trouvé et ensuite appeler setattr sur cet objet trouvé?
Merci!
4 réponses
vous pourriez utiliser functools.reduce
:
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
et rsetattr
sont des substituts pour getattr
et setattr
,
qui peut aussi manipuler des cordes pointillées 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
pour un parent et un enfant:
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__
C'est plus simple que les autres réponses pour ce cas d'utilisation particulier.
j'ai fait une version simple basée sur la réponse d'ubntu appelé magicattr qui fonctionne également sur les attrs, listes, et dicts en analysant et en marchant sur l'ast.
Par exemple, avec cette classe:
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])
Vous pouvez le faire
# 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
il ne vous permettra pas non plus d'appeler des fonctions ou d'attribuer une valeur puisqu'il n'utilise pas eval ou n'autorise pas les noeuds 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)')
Ok donc en tapant la question j'ai eu une idée de comment faire ceci et il semble fonctionner très bien. Voici ce que j'ai trouvé:
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
je suppose que cela résout ma question, mais je suis toujours curieux s'il y a une meilleure façon de le faire?
j'ai l'impression que cela doit être quelque chose de assez commun en OOP et python, donc je suis surpris que gatattr et setattr ne supportent pas cela nativement.