How to create a limited set of instanceses of a class

Alex Martelli aleax at mac.com
Sun Jul 2 13:01:19 EDT 2006


madpython <madpython at gmail.com> wrote:

> Thanks Alex and Scott for your lead. It would've taken me forever
> trying to figure it out by myself :)
> 
> I am affraid I didn't specify initially one thing and that led to a
> confusion: there is no need to pick an instance from the weakref
> dictionary, just return None if there are already 5 instances. But on
> the other hand if a hardref to an object was deleted, it's place can be
> taken by another one.

And the latter issue is what the use of weakref in both responses was
about.

> Here's what i mean (and where the problem is):
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> #build a list of 5 elements
> instList=[]
> for i in range(7):
>       ainst=A()
>       if ainst:
>               instList.append(ainst)

Note that this is quite sloppy:

-- test "if ainst is None:" -- no excuse to use just "if ainst:"
-- at the end of the loop ainst remains bound -- "del ainst" to avoid
   ainst being an extra reference to the instance

neither of these explains your bug, but -- just tighten up your code,
it's a good idea!-)

> 
> for i in range(5):
>       instList.remove(instList[0]) #here the hardref is deleted

SUPER sloppy!  Just "del instList[0]" for the same effect much better
obtained.

>       ainst=A()
>       while not ainst:
>               #make shure that ainst is not NoneType

Again, "while ainst is None:" would be far better.

>               gc.collect()
>               time.sleep(1)   #wait 1 sec for gc() to clean the memory

Useless, gc.collect() is synchronous *AND* only cleans up CYCLIC garbage
anyway -- unless instances of A have reference loops, both lines are
useless (the sleep is useless in ANY case).

>               ainst=A()       #try again
>       instList.append(ainst) #new object added
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> the priblem is that ~3 out of 10 times the test part run into infinite
> loop because of unsatisfied condition (while not ainst:) - memory
> cannot be freed therefore new instance of A isn't permitted.

Your test code below does NOT do what your code here does!  Instead it
removes (in the worst possible way, rather than cleanly, but, no matter)
a RANDOM reference -- which may happen to be the same as "item" had left
over from the previous run... because of a printing loop that is in your
sample below and not here... which is where the sloppiness catches up on
you.  Specifically, look at this code from the sample that you had
below:

>     #delete and recreate an arbitrary element in the instList
>     for i in range(len(instList)):
>         instList.remove(instList[random.choice(range(len(instList)))])
>         ainst=A()
>         while not ainst:        #here is an unstable part
>             ainst=A()           #sometimes the loop becomes infinite
>             print gc.collect()  #decpite the explicit call for gc() to start
>             time.sleep(1)
>             print "*", len(instList), len(A._weakrefdict)
>         instList.append(ainst)
>         for item in instList:
>             print item.getId()," -- ",item.instCounter
>             #print "-------> ",item
>         print "++++++++++++++++++++++++++++"

after the printing loop, name 'item' remains bound to the object that is
last element of instList -- so if that one just happens to be the
element you remove in that horrid first line of the main (outer) loop, 5
instances of class A nevertheless remain alive and therefore ainst will
be None forevermore, despite all the useless calls to gc.collect and
sleep.

A decent way to code this functionality would be:

    # delete and recreate an arbitrary element in the instList
    for i in range(len(instList)):
        del instList[random.randrange(len(instList))]
        instList.append(A())
        for item in instList:
            print item.getId()," -- ",item.instCounter
        del item
        print "++++++++++++++++++++++++++++"

It would be nice to also print len(instList) and the length of the
weakref dictionary of class A, to doublecheck things, but you've made
life extremely hard for yourself by naming the latter with TWO leading
underscores instead of one, thus asking Python to name-mangle it to make
it very inconvenient to access.  Avoid the two-leading-underscores
construct (use just ONE leading underscore -- no mangling, no problems)
unless and until you are positive that you know exactly what you're
doing and are certain that you need the two underscores in a given
specific case -- do *NOT* "default" to using two underscores and make
life uselessly hard for yourself in terms of introspection and
debugging.

And, read up on such issues as "del somelist[index]" being WAY better
than "somelist.remove(somelist[index])" and the way names, references,
objects and garbage collection work in Python...


Alex



More information about the Python-list mailing list