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!

21
demandé sur TPB 2015-07-02 04:29:42

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
37
répondu unutbu 2018-04-13 14:22:48

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.

4
répondu ChaimG 2017-09-07 23:05:00

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)')
2
répondu frmdstryr 2018-06-04 22:31:50

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.

1
répondu TPB 2015-07-02 02:41:45