4

I'm fairly new to Python. In programming a lot of PHP recently I got used to some creative use of __get and __set "magic" methods. These were only called when a public variable of the class wasn't present.

I'm trying to replicate the same behavior in Python, but seem to be failing miserably. Given there doesn't seem to be a way to actually define class variables in a C++/PHP way, when I try to use variables normally within my class (i.e. via self) it ends up calling __getattr__!

How do I define attributes of my class that I don't want affected by __getattr__?

Some sample code of what I'm trying to do is below, where I'd want self.Document and self.Filename NOT to invoke __getattr__.

Thanks for the help!

class ApplicationSettings(object):
    RootXml = '<?xml version="1.0"?><Settings></Settings>'

    def __init__(self):
        self.Document = XmlDocument()
        self.Document.LoadXml(RootXml)

    def Load(self, filename):
        self.Filename = filename
        self.Document.Load(filename)

    def Save(self, **kwargs):
        # Check if the filename property is present
        if 'filename' in kwargs:
            self.Filename = kwargs['filename']

        self.Document.Save(self.Filename)

    def __getattr__(self, attr):
        return self.Document.Item['Settings'][attr].InnerText

    def __setattr__(self, attr, value):
        if attr in self.Document.Item['Settings']:
            # If the setting is already in the XML tree then simply change its value
            self.Document.Item['Settings'][attr].InnerText = value
        else:
            # Setting is not in the XML tree, create a new element and add it
            element = self.Document.CreateElement(attr)
            element.InnerText = value

            self.Document.Item['Settings'].AppendChild(element)
Kyle Johnson
  • 1,605
  • 2
  • 17
  • 26
  • 1
    If the attribute is found by the normal lookup process, `__getattr__` should not be called. – Wooble Aug 19 '11 at 00:02
  • 1
    Don't put the answer into the question, post it as an answer. Then, after two days, accept it. – agf Aug 19 '11 at 00:45
  • `object.__setattr__` isn't quite as "hoaky" as it seems - thing about it this way: if you hadn't overridden `__setattr__` yourself, `object.__setattr__` is the function that would actually get called, so in reality you're doing what happens a lot in subclassing - calling the superclass's implementation for all invocations you don't want to overide in the subclass. – Eli Collins Aug 19 '11 at 01:05

4 Answers4

1

__getattr__ is only invoked when Python cannot find the attribute in the instance itself or in any of its base classes. The simple solution is to add Document and Filename to the class so it is found.

class ApplicationSettings(object):
    Document = None
    Filename = None
    RootXml = '<?xml version="1.0"?><Settings></Settings>'
    ...
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
0

What you really need here is a descriptor. Hooking __getattr__ and __setattr__ like that is not really recommended method.

Keith
  • 42,110
  • 11
  • 57
  • 76
0

I would use Properties. Using the @property decorator makes it looks even nicer.

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

You can then access C.x and it will automatically call the getter for x, and automatically call the setter for x when you assign to C.x.

Aphex
  • 7,390
  • 5
  • 33
  • 54
0

Apparently if I check for the attribute name in __setattr__ I can then call object's __setattr__ for the attributes I want to use normally. This feels pretty hoaky, but works.

    def __setattr__(self, attr, value):
        # Check for attributes we want to store normally
        if attr == 'Document' or attr == 'Filename':
            object.__setattr__(self, attr, value)
        # If the setting is already in the XML tree then simply change its value
        elif attr in self.Document.Item['Settings']:
            self.Document.Item['Settings'][attr].InnerText = value
        # Setting is not in the XML tree, create a new element and add it
        else:
            element = self.Document.CreateElement(attr)
            element.InnerText = value

            self.Document.Item['Settings'].AppendChild(element)
Kyle Johnson
  • 1,605
  • 2
  • 17
  • 26