I try to run one example from the book "Python Essential Reference" involving observer pattern, but there is a problem with attribute. When the AccountObserver executes __del__
the error raises - Object has no attribute 'observers'. I don't know what wrong is with the code, so any help would be very appreciate.
class Account(object):
def __init__(self, name, balance):
self.name = name
self.balance = balance
self.observers = set()
def __del__(self):
for ob in self.observers:
ob.close()
del self.observers
def register(self, observer):
self.observers.add(observer)
def unregister(self, observer):
self.observers.remove(observer)
def notify(self):
for ob in self.observers:
ob.update()
def withdraw(self, amt):
self.balance -= amt
self.notify()
class AccountObserver(object):
def __init__(self, theaccount):
self.theaccount = theaccount
self.theaccount.register(self)
def __del__(self):
self.theaccount.unregister(self)
del self.theaccount
def update(self):
print("Balance is %0.2f" % self.theaccount.balance)
def close(self):
print("Account no longer in use")
a = Account("Ketty", 200000)
a_mama = AccountObserver(a)
a_tata = AccountObserver(a)
a.unregister(a_mama)
a.withdraw(10)
And the output:
Balance is 199990.00
Account no longer in use
Exception ignored in: <bound method AccountObserver.__del__ of <__main__.AccountObserver object at 0x024BF9F0>>
Traceback (most recent call last):
File "F:\Projects\TestP\src\main.py", line 28, in __del__
File "F:\Projects\TestP\src\main.py", line 13, in unregister
AttributeError: 'Account' object has no attribute 'observers'
Exception ignored in: <bound method AccountObserver.__del__ of <__main__.AccountObserver object at 0x024BFEB0>>
Traceback (most recent call last):
File "F:\Projects\TestP\src\main.py", line 28, in __del__
File "F:\Projects\TestP\src\main.py", line 13, in unregister
AttributeError: 'Account' object has no attribute 'observers'
Python cleans out the module when the interpreter exits. At that point all instances and classes are deleted, and that means that Account.__del__
can run before AccountObserver.__del__
. The order in which the classes are cleared depends on the global namespace dictionary order, which is random thanks to the random hash seed used. Account.__del__
deletes self.observers
so any later call to account.unregister()
will raise an AttributeError
.
Your code relies on the classes and attributes all still being there when the module exits. That means you can get both KeyError
errors (as a_mama
was already unregistered), or AttributeError
as the self.observers
attribute is already cleared (because Account.__del__
cleared it).
There is a big fat warning in the object.__del__
documentation:
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.
The work-around is to make your __del__
method more robust in the face of such exceptions:
def unregister(self, observer):
try:
self.observers.remove(observer)
except (KeyError, AttributeError):
# no such observer, or the observers set has already been cleared