10

I have a peculiar python problem. During the course of execution of my gtk python application, some of my class objects mysteriously lose attributes, causing some of the functionality of my program to break.

It's hard to give a sense of why this might happen - I never intentionally delete attributes, and the classes in question inherit from a class I wrote myself (and no others).

I can trigger the problem by doing a certain action repeatedly (for example generating many calls to the add_card method - either by clicking madly or by opening a file, causing add_card to be called twenty or so times)

I am really at a loss, and I wish I had more information I thought useful to give you.

What can cause a python object to lose attributes?

EDIT, Re. Questions:

Here are example tracebacks related to the two attributes I 'lose':

Traceback (most recent call last):
  File "lib/genericlist.py", line 90, in cursor_changed
    if self.viewer:
AttributeError: 'DeckerRunnerList' object has no attribute 'viewer'



Traceback (most recent call last):
  File "lib/genericlist.py", line 100, in row_activated
    selection = self.TABLE_NAME+"&&"+text
AttributeError: 'DeckerRunnerList' object has no attribute 'TABLE_NAME'

And here is where they are set:

class DeckerGenericList(object):   

    def __init__(self, database, viewer=None, deck=None):

        super(DeckerGenericList, self).__init__()

        self.database = database
        self.viewer = viewer
        self.deck = deck
        #TABLE_NAME is set in the subclass constructor

This particular subclass doesen't call it's superclass __init__ so the attribute sets are duplicated in the subclass:

class DeckerRunnerList(DeckerGenericList):

      def __init__(self, database, viewer=None, deck=None):

        self.database = database
        self.viewer = viewer
        self.deck = deck
        self.TABLE_NAME = "runners"

All the other subclasses of DeckerGenericList have the same issue, and they are all defined like this:

class DeckerGearList(DeckerGenericList):

    def __init__(self, database, viewer=None, deck=None):

        self.TABLE_NAME = "gear"
        #... some other class attributes

        super(DeckerGearList, self).__init__(database, viewer=viewer, deck=deck)
Sheena
  • 15,590
  • 14
  • 75
  • 113
jsj
  • 9,019
  • 17
  • 58
  • 103
  • 3
    Are you using threads? Can you show some code? – Keith Nov 10 '12 at 11:04
  • How and *when* do you set these attributes? How do you know they weredeleted? – Kos Nov 10 '12 at 11:05
  • First, if you haven't already, check the type of the object to make sure it actually is an instance of you class-- I find that keeping track of types in weakly dynamically typed languages like Python to be a common source of error. – ApproachingDarknessFish Nov 10 '12 at 11:06
  • @Kos both the attrs lost are set during `__init__` and not set after. I know they are deleted because of an AttributeError – jsj Nov 10 '12 at 11:11
  • @ValekHalfHeart Yeah the AttributeError tells me which class the object is - it is the one it's supposed to be – jsj Nov 10 '12 at 11:12
  • The attribute error could come from something else "underneath". Could you at least show a traceback? – Keith Nov 10 '12 at 11:12
  • You could try making it read-only with a property. – Keith Nov 10 '12 at 11:16
  • well, obviously something called `del obj.viewer` would do it. Is there code anywhere like that? – Keith Nov 10 '12 at 11:19
  • @Keith nothing at all, and I even overrode `__delattr__`, printing a stacktrace - it is never called – jsj Nov 10 '12 at 11:20
  • hm, well hard to tell from this. I've never seen that. – Keith Nov 10 '12 at 11:22
  • 1
    Do you call ``super(…).__init__`` in the subclass constructor? – Jonas Schäfer Nov 10 '12 at 11:40
  • 2
    Where's `DeckerRunnerList` defined? – Eric Nov 10 '12 at 11:40
  • @JonasWielicki for that particular class I don't because it behaves a little differently, but that attribute is set in the subclass constructor. Plus I get exactly the same problem with all of the DeckerGenericList's subclasses – jsj Nov 10 '12 at 11:51
  • 3
    FWIW, `self.TABLE_NAME` should probably be a class attribute, not an instance attribute – Eric Nov 10 '12 at 11:59
  • 3
    Post an example code that actually uses the classes and raises that error. Possibly a minimal example that shows the behaviour. Without this how are we supposed to know what are you doing? – Bakuriu Nov 10 '12 at 12:21
  • I'd look at `vars(self).keys()` just before the lines that raise the `AttributeError` just to make sure the attribute is really missing. – Eryk Sun Nov 10 '12 at 12:21
  • Is `DeckerGenericList.database` a property (data descriptor) that can call `cursor_changed` and `row_activated` before `viewer` and `TABLE_NAME` are assigned? – Eryk Sun Nov 10 '12 at 12:41
  • My guess is that you are using properties somewhere and because of that, are trying to use the variables before they are defined. Can you surround line 90 with a: `try: [...other code...]; except AttributeError: import pprint; pprint.pprint(self.__dict__)` – Wolph Nov 10 '12 at 12:44
  • 3
    It looks like it might be something distinctly pygtk flavored... check this out: http://stackoverflow.com/questions/5346578/how-can-i-find-out-why-when-a-python-object-loses-attributes – Sheena Nov 10 '12 at 14:42
  • Garbage collector bug? Here's an old bug that was apparently "fixed": https://bugzilla.gnome.org/show_bug.cgi?id=92955 – Steven T. Snyder Feb 08 '13 at 21:26

3 Answers3

3

PyQT (and therefore maybe also PyGtk or other framework) has the weird behaviour that actually raising an AttributeError somewhere within/below the attribute code will actually make python report that the called attribute on that object does not exist.

There is a (somewhat more official) story somewhere about why this is (and is actually right and understandable behaviour). It has something to do with __getattr__ being defined on a base class: The meganism by which that works is that if calling the attribute on an object results in an AttributeError, the __getattr__ method is called. But that method has no idea what 'attribute' was actually not found. And since the (PyQt.QObject) __getattr__ was designed to implement 'specific' attributes, it decides to raise another AttributeError mentioning the 'called' attribute.

Anyway, what could be is that you are inheriting from an object which uses __getattr__ somewhere and that your own attribute code does call (another) attribute which does, indeed, not exist. What you can do to check this is:

@property
def myproperty:
   try:
      doStuff..
   except AttributeError as e:
      print "Got ya! It is withing the attribute:", repr(e)
      raise ValueError("Got an AttributeError within my attribute!")

Update/Note: Also, if you are implementing __getattr__ yourself on your DeckerGenericList object, be carefull with it for this very same reason! Raising AttributeError's within __getattr__ will most of the time lead to seemingly weird behaviour like this. Note that there is no easy fix: The 'real' AttributeError only carries a string message (and not specificly the actual attribute name), but much more important: the __getattr__ function never sees the original AttributeError in the first place.

knifter
  • 63
  • 5
2

Well you can use property and inspect module to trace changes to attribute like this:

import sys
import inspect

def from_where_called():
    info = inspect.getframeinfo(sys._getframe(2))
    code = info.code_context[0] if info.code_context else ''
    return '%s:%s %s' % (info.filename, info.lineno, code)

def add_watched_attribute(name):  
    def attr_watch_get(self):
        value = getattr(self, '_' + name, 'unset')
        print from_where_called(), name, 'is', value
        return value

    def attr_watch_set(self, value):
        print from_where_called(), name, 'set to', value
        setattr(self, '_' + name, value)

    def attr_watch_delete(self):
        print from_where_called(), name, 'deleted'
        delattr(self,'_' + name)

    sys._getframe(1).f_locals[name] = property(
        attr_watch_get, attr_watch_set, attr_watch_delete
    )


class InspectedClass(object):
    add_watched_attribute('victim')

    def __init__(self):
        self.victim = 2

    def kill(self):
        del self.victim


x = InspectedClass()
x.victim = 'asdf'
x.kill()

Or you can use sys.settrace from this answer: https://stackoverflow.com/a/13404866/816449

Community
  • 1
  • 1
Bunyk
  • 7,635
  • 8
  • 47
  • 79
  • I just used this code in an attempt to further investigate http://stackoverflow.com/questions/5346578/how-can-i-find-out-why-when-a-python-object-loses-attributes and it was very helpful. Thanks! – Steven T. Snyder Feb 08 '13 at 21:20
1

I think it's a garbage collector bug. I had a similar problem and I think I have narrowed it down to the GC.

Try adding a list extra_references = [] to the top of the module containing the class which loses attributes. In the __init__ method for the class, add the following code:

global extra_references
extra_references.append(self)

That will ensure that there is always a reference to the object outside of GObject.

If the problem goes away, then Python's garbage collector is eating up your object before you're done with it. Smells a lot like this bug (which was supposedly fixed): https://bugzilla.gnome.org/show_bug.cgi?id=92955

Steven T. Snyder
  • 5,847
  • 4
  • 27
  • 58