[Tutor] Clueless

Marilyn Davis marilyn at deliberate.com
Sun Mar 14 23:12:25 EST 2004


On Sun, 14 Mar 2004, Alan Gauld wrote:

> > I can't even give this one a good subject line, I'm so clueless.
> 
> I'm not totally sure on this one but a scan of the code shows 
> up one strangeness at least:
> 
> > 
> >   Logger                       object
> >    /|\                        /|\  /|\
> >     |                          |    |
> >   Watched  --------------------     |
> >    /|\                            list
> >     |                              /|\
> >     |                               |
> >   WatchedList  ---------------------
> > 
> > All is cool as long as I don't implement Logger.__del__
> > 
> 
> WAtched is a subnclass of Logger so inherits Loggers del method.
> But...
> 
> > class Watched(object, Logger):
> >     def __init__(self, log_name):
> >         self.log = Logger(log_name + '.log')

Oh!  Duh!  Good eye!  

> 
> INstead of initisalising the inherited file you choose to also 
> have a Logger object have a reference to a logger. Thats OK of 
> course but it does mean we eff4ectively have two Logger instances 
> in the program each with a __del__ defined, but only one of them 
> with file defined...

Not wonder it was so confused.

And that's all it was!

Alan, you are great!

And thank you Gonçalo Rodrigues.

> Python calls __del__ the object is about to be destroyed (it's
> refcount is 0). There are problems when the object participates in a
> cycle - in that case Python's garbage collector refuses to dispose of
> the cycle because of the existence of __del__.
> 
> All told, this means in practice that __del__ is almost always a bad
> choice for managing external resources. Just free them explicitely --
> that is, call close when you're done with the resource.

Happily, python wasn't confused without reason.  I can keep my call to
close in Logger.__del__ and all is well.

> 
> __getattr__ will *never* be called so this is not needed. Recall:
> __getattribute__ gets called on *every* attribute lookup, so it
> *always* overrides __getattr__.

No.  __getattribute__ calls __getattr__ when the attribute doesn't
exist.  I was surprised by this too.  

Here I make both methods verbose:

bash-2.05a$ python2.2 -i att5.py
getattribute called on __dict__
>>> assets
[]
>>> x = assets.x
getattribute called on x
getattr called on x
getattribute called on logit
getattribute called on log_file
getattribute called on log_file
getattribute called on log_file
getattribute called on __dict__
>>> del assets
>>> 

> This means that, even for  self.__dict__
> __getattribute__ is called! It is a very tricky method, easy to get
> into infinite loops.

Yes.  It was a difficult.  This code prevented loops on self.logit
and __dict__ by not allowing the call to self.logit or self.__dict__
when the attribute was self.logit or self.__dict__.  Note that this
code is duplicated 3 times.  I couldn't find any way to put it in
a method that didn't loop.  And I don't know why.

    def __getattribute__(self, attrname):
        print 'getattribute called on', attrname
        ret = object.__getattribute__(self, attrname)
        if attrname[:3] != 'log' and attrname[:2] != '__':
            self.logit(attrname + ' accessed, value = ' + str(ret))
        return ret

Anyhow, here's the whole (working) code, in case anyone has any more
thoughts about it.  It's sort of silly because it doesn't log accesses
to the list.  All criticisms and suggestions gratefully accepted.

#!/usr/bin/env python2.2
'''New style classes have __getattribute__ which intercept
all references to attributes, ones that exist and don't
exist.  We'll make a Watched class that logs all accesses and
assignments.'''

import time

class Logger(object):
    def __init__(self, name):
        self.log_file = open(name, 'w')

    def logit(self, entry):
        self.log_file.write(time.ctime(time.time()))
        self.log_file.write('\n' + entry + '\n')
        self.log_file.flush()

    def __del__(self):
        self.log_file.close()

class Watched(object, Logger):

    def __init__(self, log_name):
        Logger.__init__(self, log_name + '.log')
        
    def __getattribute__(self, attrname):
        print 'getattribute called on', attrname
        ret = object.__getattribute__(self, attrname)
        if attrname[:3] != 'log' and attrname[:2] != '__':
            self.logit(attrname + ' accessed, value = ' + str(ret))
        return ret

    def __getattr__(self, attrname):
        '''Called when attrname is not in __dict__ already.'''
        print 'getattr called on', attrname
        if attrname[:3] != 'log' and attrname[:2] != '__':
            self.logit('Attempt to access ' + attrname + ' which does not exist.')
        self.__dict__[attrname] = None
        return None

    def __setattr__(self, attrname, value):
        if attrname[:3] != 'log' and attrname[:2] != '__':
            try:
                self.logit(attrname + ' changed from ' + self.__dict__[attrname] + ' to ' + str(value))
            except KeyError:
                self.logit(attrname + ' created = ' + str(value))
        self.__dict__[attrname] = value

class WatchedList(Watched, list):
    def __init__(self, log_name, init_list = None):
        Watched.__init__(self, log_name)
        if init_list:
            list.__init__(self, init_list)

assets = WatchedList('assets')

############################################################
#  OUTPUT:
# bash-2.05a$ python2.2 -i att5.py
# >>> assets
# []
# >>> assets += [1,2,3]
# >>> assets.x = 3
# >>> assets.sort()
# >>> assets
# [1, 2, 3]
# >>> del assets
# >>> 
# bash-2.05a$










More information about the Tutor mailing list