51

This code...

class Person:
    num_of_people = 0

    def __init__(self, name):
        self.name = name
        Person.num_of_people += 1

    def __del__(self):
        Person.num_of_people -= 1

    def __str__(self):
        return 'Hello, my name is ' + self.name

cb = Person('Corey')
kb = Person('Katie')
v = Person('Val')

Produces the following error...

Exception AttributeError: "'NoneType' object has no attribute 'num_of_people'" in <bound method Person.__del__ of <__main__.Person object at 0x7f5593632590>> ignored

But this code does not.

class Person:
    num_of_people = 0

    def __init__(self, name):
        self.name = name
        Person.num_of_people += 1

    def __del__(self):
        Person.num_of_people -= 1

    def __str__(self):
        return 'Hello, my name is ' + self.name

cb = Person('Corey')
kb = Person('Katie')
vb = Person('Val')

The only difference I see is the last variable name is "vb" vs. "v".

I am leaning Python and am working on the OOP stuff now.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Corey
  • 816
  • 9
  • 19
  • 2
    @StevenRumbalski: In short, yes. But only at interpreter exit. – Martijn Pieters Apr 04 '14 at 13:55
  • 6
    The first code does not produce that exception. Show your full traceback. (Correction: it doesn't produce that exception in Python 3.3 or higher. In 3.2 it does.) – Wooble Apr 04 '14 at 13:55
  • @Wooble Nah! That's what i was missing.. – aIKid Apr 04 '14 at 14:00
  • 3
    @Wooble: That's because dictionary hashing for strings is randomized in 3.3. It'll happen in 3.3 too, when the stars align just right for the keys to collide in the right order. In other words, re-run your test several times and you'll see it happen on **some** runs. – Martijn Pieters Apr 04 '14 at 14:04
  • 4
    @Wooble: Last but not least, you certainly won't see this error in CPython 3.4, as it has a new [safe object finalization codepath](https://docs.python.org/3/whatsnew/3.4.html#whatsnew-pep-442) that removes the reason for this error altogether. – Martijn Pieters Apr 04 '14 at 14:12
  • Note: I wouldn't say that what you see is an "error". It's a **ignored** exception, and it is also a *correctly* ignored exception, and as such I'd say it's a *warning* more then an error. If you didn't mess with `__del__` you wouldn't have this problem, and I believe the code you have written is *misusing* `__del__`. In fact in python<3.4 you could easily create some cycles of references so that your `num_of_people` counter is off. – Bakuriu Apr 04 '14 at 14:35
  • Not just Python3. In Python 2.7.3 I get two such errors for the first example and one for the second. – Evgeni Sergeev Apr 05 '14 at 00:52

1 Answers1

59

Yes, although it is not the so much the variable name that causes this, not directly.

When Python exits, all modules are deleted too. The way modules are cleaned up is by setting all globals in a module to None (so those references no longer refer to the original objects). Those globals are keys in a dictionary object, and as dictionaries are ordered arbitrarily, renaming one variable can change the order in which variables are cleared.

When you renamed v to vb, you altered the order in which variables are cleared, and now Person is cleared last.

One work-around is to use type(self).num_of_people -= 1 in the __del__ method instead:

def __del__(self):
    type(self).num_of_people -= 1

because the instance will always have a reference to the class still, or test if Person is not set to None:

def __del__(self):
    if Person is not None:
        Person.num_of_people -= 1

Two notes:

  • CPython 3.4 no longer sets globals to None (in most cases), as per Safe Object Finalization; see PEP 442.

  • CPython 3.3 automatically applies a randomized hash salt to the str keys used in a globals dictionary; this makes the behaviour you observed even more random, merely re-running your code several times may or may not trigger the error message.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 2
    Ugh.. The answer is brilliant, but I don't really understand this. What do you mean by 'on intepreter exit'? Can you please explain? – aIKid Apr 04 '14 at 13:58
  • 3
    @aIKid: When Python exits, (or if you delete the module by removing all references to it and deleting it from `sys.modules`, but that's not often done), the module `__del__` is called, which then proceeds by first clearing all globals in the module by rebinding their names to `None`. – Martijn Pieters Apr 04 '14 at 14:00
  • So the exception is only raised when we exit the interpreter.. <- That's false, right? – aIKid Apr 04 '14 at 14:02
  • 1
    @aIKid: this should only happen when you exit the interpreter, *or* you delete the module explicitly. – Martijn Pieters Apr 04 '14 at 14:03
  • Wow. I don't know there's such thing! Well, thanks! – aIKid Apr 04 '14 at 14:04
  • 1
    This kind of stuff is why I love StackOverflow. +1, very interesting. – asteri Apr 04 '14 at 14:43
  • great technical answer, could you summarize it at a higher level for me? – Corey Apr 04 '14 at 15:15
  • 2
    @Corey: there isn't really a higher level here; the bottom line is that during finalization, globals like `Person` and `v` and `vb` have `None` assigned to them, so you cannot count on `Person` being the class still in `__del__`. – Martijn Pieters Apr 04 '14 at 15:22
  • 3
    @sds: finalization is finicky; by setting globals to `None` many common circular references are broken properly without invoking (slow and entirely unneeded) resizes of the `globals` dictionaries. The finally did find a better way for Python 3.4, so this whole thing is no longer a problem with that version. – Martijn Pieters Apr 04 '14 at 18:03