2015-07-02 17 views
18

Dies ist wahrscheinlich ein einfaches Problem, so hoffnungsvoll ist es einfach für jemanden, auf meinen Fehler hinzuweisen, oder wenn das überhaupt möglich ist.getattr und setattr auf verschachtelten Objekten?

Ich habe ein Objekt, das mehrere Objekte als Eigenschaften hat. Ich möchte dynamisch in der Lage sein, um die Eigenschaften dieser Objekte festgelegt wie folgt:

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__ 

Die Ausgabe lautet:

{ ‚Haustier‘: < Haupt .pet Objekt bei 0x10c5ec050>, ‚Wohnort‘ : < Haupt .Residence Objekt auf 0x10c5ec0d0>, 'pet.name': 'Sparky', 'residence.type': 'Apartment'}

Wie Sie, anstatt den Namen Attribut sehen können, auf die gesetzt pet-Objekt der Person, ein neues Attribut "pet.name" ist crea ted.

Ich kann person.pet nicht auf setattr setzen, weil andere untergeordnete Objekte mit der gleichen Methode gesetzt werden, die einen Text analysiert und die Objektattribute ausfüllt, wenn/wenn ein relevanter Schlüssel gefunden wird.

Gibt es eine einfache/eingebaute Möglichkeit, dies zu erreichen?

Oder vielleicht muss ich eine rekursive Funktion schreiben, um die Zeichenfolge zu analysieren und Getattr mehrere Male aufrufen, bis das erforderliche Kindobjekt gefunden wird, und dann Setattr für dieses gefundene Objekt aufrufen?

Vielen Dank!

Antwort

28

Sie könnten functools.reduce verwenden:

import functools 

def rsetattr(obj, attr, val): 
    pre, _, post = attr.rpartition('.') 
    return setattr(rgetattr(obj, pre) if pre else obj, post, val) 

sentinel = object() 
def rgetattr(obj, attr, default=sentinel): 
    if default is sentinel: 
     _getattr = getattr 
    else: 
     def _getattr(obj, name): 
      return getattr(obj, name, default) 
    return functools.reduce(_getattr, [obj]+attr.split('.')) 

rgetattr und rsetattr sind Drop-in-Ersatz für getattr und setattr, die auch gepunkteten attr Strings verarbeiten kann.


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) 

sentinel = object() 
def rgetattr(obj, attr, default=sentinel): 
    if default is sentinel: 
     _getattr = getattr 
    else: 
     def _getattr(obj, name): 
      return getattr(obj, name, default) 
    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 
+0

Wow, das war etwas, was ich nicht wusste. –

+1

Vielen Dank für die Antwort! Das ist cool, ich liebe, wie Sie in 5 Zeilen tun können, was mich 30 dauerte. Sehr saubere und prägnante Lösung! – TPB

+1

Schöne Antwort! –

1

Ok, während der Eingabe der Frage hatte ich eine Idee, wie dies zu tun ist und es scheint gut zu funktionieren. Hier ist, was ich kam mit:

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 

Ich denke, das ist meine Frage löst, aber ich bin immer noch neugierig, ob es ein besserer Weg, dies zu tun?

Ich habe das Gefühl, dass dies in OOP und Python ziemlich üblich sein muss, also bin ich überrascht, dass gatattr und setattr das nativ nicht unterstützen.

+0

Der Name des letzten Attributs final_attribute kann mehrmals vorkommen. Zum Beispiel ist 'p.foo.foo' legal. Daher kann die Bedingung 'part == final_attribute' zu ​​früh ausgelöst werden. – unutbu

+0

Ahh sehr guter Punkt, wechselte ich auf die Überprüfung des Index, der das Problem lösen sollte.Danke, dass du darauf hingewiesen hast! – TPB

1

Für einen Elternteil und ein Kind:

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__ 

Dies ist einfacher als die anderen Antworten für diesen speziellen Anwendungsfall.