Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using python @property in __init__ , gives attribute error

Is my 'fix' the correct / good practice method?

I have a class Text(), that caches it's surface to render, unless font/size changes. (I left the font property from the snippet)

If I attempt to use self.size in __init__ , it will fail:

Traceback (most recent call last):
  File "temp.py", line 21, in <module>
    t = Text("arial", 6)
  File "temp.py", line 3, in __init__
    self.size = size
  File "temp.py", line 16, in size
    if self._size != px:            
AttributeError: 'Text' object has no attribute '_size'

I was able to assign self._text in __init__ , but I believe that's wrong, since:

  1. Ignores the setter, which may have extra checks
  2. code might break if _text changes, but could be ok if it uses the property instead.

Code with error:

class Text(object):
    def __init__(self, font, size=12):
        self.size = size
        self.font = font
        self.dirty = False

    def draw(self):
        print "Draw: ", self.size, self.font, self.dirty

    @property
    def size(self): 
        return self._size

    @size.setter
    def size(self, px):
        if self._size != px:            
            self._size = px
            self.dirty=True

if __name__  == "__main__":
    t = Text("arial", 6)
    t.draw()
    t.size = 8
    t.draw()

My fix:

class Text(object):
    def __init__(self, font, size=12):
        self.size = size
        self.font = font
        self.dirty = False

    def draw(self):
        print "Draw: ", self.size, self.font, self.dirty

    @property
    def size(self): 
        return self._size
    @size.setter
    def size(self, px):
        try:
            if self._size != px:            
                self._size = px
                self.dirty=True
        except:
            self._size = 14
            self.dirty = True

if __name__  == "__main__":
    t = Text("arial", 6)
    t.draw()
    t.size = 8
    t.draw()
like image 619
ninMonkey Avatar asked Oct 26 '11 20:10

ninMonkey


2 Answers

   try:
        if self._size != px:            
            self._size = px
            self.dirty=True
    except:
        self._size = 14
        self.dirty = True

The except is a bad idea because you catch all exceptions. You might accidentally catch some other exception and ignore it. That would be a bad idea. Python throws exceptions for all sorts of reason and you'll end up masking a bug. For the same reason, you should put as little code in the try block as possible.

One approach would be:

try:
   size_changed = self._size != size
except AttributeError:
   size_changed = True

if size_changed:
    self._size = size
    self.dirty = True

But a cleaner way to solve this problem:

def __init__(self, font, size=12):
    # store something in the hidden attribute
    # so it will work as expected
    self._size = None

    self.size = size
    self.font = font
    self.dirty = False
like image 172
Winston Ewert Avatar answered Oct 14 '22 01:10

Winston Ewert


The pattern that I use is:

class Text(object):
    def __init__(self, font, size=12):
        self.font = font
        self._size = size
        self.dirty = False
    @property
    def size(self):
        return self._size
    @size.setter
    def size(self, px):
        if self._size != px:
            self._size = px
            self.dirty = True

The problem that you were running in to is that your setter was referring to an attribute that you weren't creating until the setter was called -- self._size doesn't exist until you have assigned something to it. Generally, I use properties that have nice names (like size) and store the data in hidden attributes (e.g., _size). In the initializer, I only manipulate the attributes directly and never rely on properties.

In your case, setting the property has a side-effect so you have a chance of bugs creeping in. Consider what would happen if you put self.dirty = False as the first line in the initializer.

like image 42
D.Shawley Avatar answered Oct 14 '22 01:10

D.Shawley