Path: csiph.com!usenet.pasdenom.info!weretis.net!feeder1.news.weretis.net!feeder.erje.net!eu.feeder.erje.net!newsfeed.freenet.ag!news2.euro.net!newsgate.cistron.nl!newsgate.news.xs4all.nl!post.news.xs4all.nl!not-for-mail Return-Path: X-Original-To: python-list@python.org Delivered-To: python-list@mail.python.org X-Spam-Status: OK 0.000 X-Spam-Evidence: '*H*': 1.00; '*S*': 0.00; 'argument': 0.04; 'explicitly': 0.04; 'cpython': 0.05; 'method.': 0.05; 'say,': 0.05; 'sys': 0.05; 'arguments': 0.07; 'constructor': 0.07; 'objects,': 0.07; 'parameter': 0.07; 'remaining': 0.07; 'python': 0.09; 'added.': 0.09; 'deletion': 0.09; 'garbage': 0.09; 'objects.': 0.09; 'received:80.91': 0.09; 'received:80.91.229': 0.09; 'received:gmane.org': 0.09; 'received:list': 0.09; 'reference:': 0.09; 'references.': 0.09; 'returns,': 0.09; 'sys.stderr': 0.09; 'terry': 0.09; 'useless': 0.09; 'def': 0.10; 'times,': 0.13; '__del__': 0.16; 'bind': 0.16; 'constitutes': 0.16; 'cyclic': 0.16; 'doc)': 0.16; 'executed,': 0.16; 'expect,': 0.16; "function's": 0.16; 'least,': 0.16; 'namespace.': 0.16; 'pairs': 0.16; 'received:80.91.229.3': 0.16; 'received:plane.gmane.org': 0.16; 'reedy': 0.16; 'scope,': 0.16; 'scope.': 0.16; 'substitute': 0.16; 'unreachable': 0.16; '{0}': 0.16; 'wrote:': 0.17; 'circular': 0.17; 'deleted.': 0.17; 'instance': 0.17; 'instance,': 0.17; 'creates': 0.18; 'jan': 0.18; '>>>': 0.18; 'memory': 0.18; 'module': 0.19; 'mostly': 0.20; 'written': 0.20; 'import': 0.21; 'meant': 0.21; 'doc': 0.22; 'object.': 0.22; 'references': 0.23; 'seems': 0.23; 'random': 0.24; 'second': 0.24; 'header:In-Reply-To:1': 0.25; 'header:User- Agent:1': 0.26; '(which': 0.26; 'creating': 0.26; '(see': 0.27; 'am,': 0.27; 'possibly': 0.27; 'to?': 0.27; "doesn't": 0.28; 'header:X-Complaints-To:1': 0.28; '"in': 0.29; 'existence': 0.29; 'methods.': 0.29; 'parent': 0.29; 'prints': 0.29; 'objects': 0.29; 'class': 0.29; 'call.': 0.30; 'returned': 0.30; 'function': 0.30; 'code': 0.31; 'point': 0.31; 'file': 0.32; 'generally': 0.32; 'comments': 0.33; 'goes': 0.33; 'point,': 0.33; 'to:addr:python- list': 0.33; 'typically': 0.33; 'another': 0.33; 'self': 0.34; 'there': 0.35; 'add': 0.36; 'received:org': 0.36; 'created': 0.36; 'but': 0.36; 'method': 0.36; 'subject:with': 0.36; 'should': 0.36; 'skip:p 20': 0.36; 'keeps': 0.37; 'one,': 0.37; 'does': 0.37; 'two': 0.37; 'being': 0.37; 'why': 0.37; 'quite': 0.37; 'rather': 0.37; 'subject:: ': 0.38; 'object': 0.38; 'sure': 0.38; 'delete': 0.38; 'instead': 0.39; 'to:addr:python.org': 0.39; 'called': 0.39; 'where': 0.40; 'header:Received:5': 0.40; 'your': 0.60; 'different': 0.63; 'skip:n 10': 0.63; 'more': 0.63; 'other.': 0.64; 'making': 0.64; 'here': 0.65; 'therefore': 0.65; 'detail.': 0.65; 'middle': 0.66; 'million': 0.72; 'counts': 0.81; "'dead'": 0.84; 'disappear': 0.84; 'disappears': 0.84; 'leak.': 0.84; 'received:fios.verizon.net': 0.84; 'comment.': 0.91; 'constitute': 0.91 X-Injected-Via-Gmane: http://gmane.org/ To: python-list@python.org From: Terry Reedy Subject: Re: Messing with the GC Date: Sat, 19 Jan 2013 11:40:28 -0500 References: Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit X-Gmane-NNTP-Posting-Host: pool-173-75-251-66.phlapa.fios.verizon.net User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:17.0) Gecko/17.0 Thunderbird/17.0 In-Reply-To: X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Newsgroups: comp.lang.python Message-ID: Lines: 119 NNTP-Posting-Host: 2001:888:2000:d::a6 X-Trace: 1358613663 news.xs4all.nl 6939 [2001:888:2000:d::a6]:32928 X-Complaints-To: abuse@xs4all.nl Xref: csiph.com comp.lang.python:37090 On 1/19/2013 9:47 AM, Jens Thoms Toerring wrote: The code comments mostly answer your questions about what happens or does not happen and when. The comments after add more detail. > import sys > > class X( object ) : > def __init__( self, parent, cnt ) : > print( "In constructor for {0} {1}".format( self, cnt ), > file = sys.stderr ) > self.parent = parent > self.cnt = cnt > > def __del__( self ) : > print( "In destructor for {0} {1}".format( self, self.cnt ), > file = sys.stderr ) > > def foo( self ) : At this point, self is self.parent.z, which is to say, self and self.parent.z are 2 references to 1 object. The self and self.parent object refer to each other and therefore constitute a reference cycle. > print( "Before", file = sys.stderr ) > self.parent.z = X( self.parent, 2 ) # Is this bad? At this point, self.parent.z is another instance of X, breaking the existing reference cycle *and* creating a new one. Now self has just the one reference 'self' (plus another non-circular, hidden one, see below#). > print( "After", file = sys.stderr ) At this point, self goes out of scope, the CPython reference count goes to 0, and the object that was self is deleted. Other implementations typically wait longer to delete. > class Y( object ) : > def __init__( self ) : > print( "In constructor for {0}".format( self ), > file = sys.stderr ) > self.z = X( self, 1 ) At this point, self is self.z.parent, where z is an X. This is a circular reference: self and self.z reference each other. > def __del__( self ) : > print( "In destructor for {0}".format( self ), > file = sys.stderr ) > > Y( ).z.foo( ) Y() creates a reference cycle. z.foo() substitute a different instance of X in the cycle but there still is a cycle, so the Y and X objects have reference counts of 1. There are no other references to the two objects, making them unreachable and 'dead' to the program. The cyclic reference garbage collector (see the gc module doc) that is meant to delete such orphans does not delete them here because of the __del__ methods. Since gc was added, __del__ is semi-obsolete. If an object might possibly be put in a reference cycle (which is quite easy), any code that might have been put in __del__ should go in an explicitly called .close() method. > Have a look at the line with the comment. At this point the > only reference in existence to the X class instance, of which > a method is just being executed, goes out of scope. Nope, the remaining reference, 'self', stays in scope until after the function exits. That is when X1 is deleted and the deletion message printed. > Is my assumption about this flawed Yes > and there are no potential dangers? The orphaned two-object cycle constitutes a memory leak. If you called Y( ).z.foo( ) a million times, you would have a million useless pairs of objects. This is why gc was added. > Y( ).z.foo( ) > > [perhaps] a temporary second reference is created that keeps the GC > for removing the X instance... Function calls (normally) bind argument object to parameter names in the function's local namespace. That binding is a temporary reference and objects will not disappear in the middle of the call. # In CPython, at least, there is another internal reference to arguments that also disappears when the function returns, allowing the deletion of arguments without other references. >>> x = 12343252 # random large number to make sure its a new object >>> sys.getrefcount(x) 2 >>> def f(a): print(sys.getrefcount(a)) >>> f(x) 4 So print(sys.getrefcount(self)) at the top of foo prints 4 (3+1), rather than 3 (2+1), as one might expect. The +1 is explained in the doc sys.getrefcount(obj): Return the reference count of the object. The count returned is generally one higher than you might expect, because it includes the (temporary) reference as an argument to getrefcount(). (Why doesn't getrefcount add 2 instead of 1, as f seems to? I don't know, but perhaps because it is written in C rather than Python and Python code objects are different from C code.) -- Terry Jan Reedy