0

I'm trying to update an attribute of a class with setattr and a string. So for instance with this code:

class ClassA():
    def __init__(self):
        self.A = 0

class ClassB():
    def __init__(self):
        self.CA = ClassA()

CB = ClassB()
setattr(CB, "CA.A", 2)

print(CB.CA.A)

when I do setattr(CB, "CA.A", 2), it doesn't update the attribute A of the CA class inCB. Instead, it creates another attribute, as in the picture:

enter image description here in the class CB named CA.A

Then when I print print(CB.CA.A), I get a 0 value. I don't understand why this happend and if a solution exists?

Community
  • 1
  • 1
ymmx
  • 4,769
  • 5
  • 32
  • 64

1 Answers1

5

setattr() takes a literal attribute name. It does not support nested attributes; the . in the name is just a dot in the name, not an attribute separator.

Put differently, . in CB.CA.A = 2 is syntax to access attributes, not part of the attribute name. CB.CA is looked up first, and then the assignment takes place to the A attribute on the result.

You need to split on the . and use a separate getattr() call to obtain the parent object first:

setattr(getattr(CB, 'CA'), "A", 2)

You could wrap that logic into a function:

def setattr_nested(base, path, value):
    """Accept a dotted path to a nested attribute to set."""
    path, _, target = path.rpartition('.')
    for attrname in path.split('.'):
        base = getattr(base, attrname)
    setattr(base, target, value)
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Ho, I see. I like the inception function :) – ymmx Feb 06 '18 at 10:10
  • However If i try to create a variable with a dot in the name, it will give me an error. So why when I use `setattr` it creates an attribute with a dot in it name – ymmx Feb 06 '18 at 10:12
  • @ymmx: normally, syntax restricts you to valid Python identifiers (which can't include dots). That's because dots have special meaning. But `setattr()` and `getattr()` are not restricted to such names, you can use any string. – Martijn Pieters Feb 06 '18 at 10:14
  • That's a bit at odds with `operator.attrgetter` that does support dots – Mr_and_Mrs_D Apr 30 '20 at 22:02
  • 1
    @Mr_and_Mrs_D `operator.attrgetter()` is what is the special case here. – Martijn Pieters Apr 30 '20 at 23:30