Python - Observer pattern - Object has no attribute
Question: 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'
    


Answer: 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](https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHASHSEED)
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](https://docs.python.org/3/reference/datamodel.html#object.__del__):

> **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 to `sys.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
    

