Reputation: 131
I am reading the Python Essential Reference 4th ed. and I cannot figure out how to fix a problem in the following code
class Account(object):
num_accounts = 0
def __init__(self, name, balance):
self.name = name
self.balance = balance
Account.num_accounts += 1
def __del__(self):
Account.num_accounts -= 1
def deposit(self, amt):
self.balance += amt
def withdraw(self, amt):
self.balance -= amt
def inquire(self):
return self.balance
class EvilAccount(Account):
def inquire(self):
if random.randint(0,4) == 1:
return self.balance * 1.1
else:
return self.balance
ea = EvilAccount('Joe',400)
If I understand correctly, the ea object goes out of scope when the program ends and the inherited __del__
function should be called, correct? I receive a 'NoneType' object has no attribute num_accounts
in __del__
. Why doesn't it complain earlier then in the __init__
function?
Upvotes: 1
Views: 1719
Reputation: 95652
Others have answered why this happened, but as to what you should do instead, try this:
import weakref
class classproperty(object):
def __init__(self, f):
self.f = f
def __get__(self, obj, owner):
return self.f(owner)
class Account(object):
_active = weakref.WeakSet()
@classproperty
def num_accounts(self):
return len(Account._active)
def __init__(self, name, balance):
self.name = name
self.balance = balance
Account._active.add(self)
def deposit(self, amt):
self.balance += amt
def withdraw(self, amt):
self.balance -= amt
def inquire(self):
return self.balance
>>> Account.num_accounts
0
>>> a = [Account('a', 0), Account('b', 0)]
>>> Account.num_accounts
2
>>> a.pop()
<__main__.Account object at 0x02F8D650>
>>> Account.num_accounts # Interactive session is holding onto the popped account in '_'
2
>>> Account.num_accounts # but now it has gone.
1
So instead of counting how many instances exist, just keep a collection of all the current instances. The WeakSet
won't prevent them being destroyed so it will accurately track only the instances that are still alive.
Do be aware though that it is very easy for instances to stick around after you think you've lost them: if anything throws an exception then all the local variables in the stack frame will remain alive until the next exception is thrown. In this case you probably also want an explicit close()
method that you can use when someone closes the account and that explicitly removes the instance from the active set.
Upvotes: 2
Reputation: 1121594
When the interpreter exits, the Account
reference is removed before the EvilAccount()
instance is reaped. As a result, Account
is now `None.
One workaround would be to use type(self)
instead of a direct reference to the class name, but that would make the count per class, so EvilAccount
would have it's own counter:
def __init__(self, ...):
# ...
type(self).num_accounts += 1
def __del__(self):
type(self).num_accounts -= 1
The other alternative is to check if Account
still exists when __del__
is invoked; simply catch the exception:
def __del__(self):
try:
Account.num_accounts -= 1
except AttributeError:
pass # Account has already been reaped
Upvotes: 1
Reputation: 77892
From the docs:
Warning: Due to the precarious circumstances under which
__del__()
methods are invoked, exceptions that occur during their execution are ignored, and a warning is printed tosys.stderr
instead. Also, when__del__()
is invoked in response to a module being deleted (e.g., when execution of the program is done), other globals referenced by the__del__()
method may already have been deleted or in the process of being torn down (e.g. the import machinery shutting down). For this reason,__del__()
methods should do the absolute minimum needed to maintain external invariants. Starting with version 1.5, Python guarantees that globals whose name begins with a single underscore are deleted from their module before other globals are deleted; if no other references to such globals exist, this may help in assuring that imported modules are still available at the time when the__del__()
method is called.
Upvotes: 4
Reputation: 304137
When ea
goes out of scope, you have no control of how the memory is released. It appears that Account
is a reference to None
at this stage
Why do you think you need a __del__
method at all?
Upvotes: 1