Path: csiph.com!usenet.pasdenom.info!news.redatomik.org!newsfeed.xs4all.nl!newsfeed3a.news.xs4all.nl!xs4all!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.007 X-Spam-Evidence: '*H*': 0.99; '*S*': 0.00; 'else:': 0.03; 'argument': 0.05; 'none:': 0.07; 'problem?': 0.07; 'additionally': 0.09; 'attributes': 0.09; 'callback': 0.09; 'constructor': 0.09; 'extends': 0.09; 'function,': 0.09; 'method,': 0.09; 'none)': 0.09; 'references,': 0.09; 'todo:': 0.09; 'def': 0.12; "wouldn't": 0.14; '(it': 0.16; '49,': 0.16; '__new__': 0.16; 'closures': 0.16; 'hashable': 0.16; 'hashable,': 0.16; 'message):': 0.16; 'objects.': 0.16; 'previously,': 0.16; 'simplifies': 0.16; 'storing': 0.16; 'typeerror:': 0.16; 'underlying': 0.16; 'appropriate': 0.16; 'wrote:': 0.18; 'feb': 0.22; '>>>': 0.22; 'import': 0.22; 'mon,': 0.24; 'looks': 0.24; "i've": 0.25; 'references': 0.26; 'skip:" 30': 0.26; 'suggested': 0.26; 'pass': 0.26; 'header:In-Reply-To:1': 0.27; 'function': 0.29; 'am,': 0.29; 'wonder': 0.29; "doesn't": 0.30; 'bigger': 0.30; 'involving': 0.30; 'message-id:@mail.gmail.com': 0.30; 'work.': 0.31; '"",': 0.31; 'assumes': 0.31; 'file': 0.32; 'class': 0.32; 'themselves': 0.32; 'url:python': 0.33; '(most': 0.33; 'ago': 0.33; 'cases': 0.33; 'framework': 0.33; 'skip:_ 10': 0.34; 'could': 0.34; 'problem': 0.35; 'anybody': 0.35; 'but': 0.35; 'received:google.com': 0.35; 'there': 0.35; 'object,': 0.36; 'method': 0.36; 'url:org': 0.36; 'should': 0.36; 'effort': 0.37; 'two': 0.37; 'sometimes': 0.38; 'skip:. 20': 0.38; 'url:library': 0.38; 'work?': 0.38; 'to:addr:python-list': 0.38; 'issue': 0.38; 'pm,': 0.38; 'previous': 0.38; 'anything': 0.39; 'recent': 0.39; 'does': 0.39; 'itself': 0.39; 'to:addr:python.org': 0.39; 'days': 0.60; 'even': 0.60; 'around.': 0.60; 'ian': 0.60; 'solve': 0.60; 'url:3': 0.61; 'took': 0.61; "you're": 0.61; "you'll": 0.62; 'making': 0.63; 'soon': 0.63; 'happen': 0.63; 'taking': 0.65; 'management': 0.65; 'talking': 0.65; 'reply': 0.66; 'determine': 0.67; 'mar': 0.68; '26,': 0.68; 'reads': 0.68; 'home.': 0.72; 'subject:Design': 0.78; '2015': 0.84; '4:00': 0.84; 'avoids': 0.84; 'completely,': 0.84; 'ian,': 0.84; 'important;': 0.84; 'subject:thought': 0.84; 'expires': 0.91 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :content-type:content-transfer-encoding; bh=6OJ72zdNHKBYaXn/9/o32XJP31U/dFF2nYVUiOZLigA=; b=NcOZACeuGxZXtPGo+7uosV2G6S38ZtkjK8gU2sWv13sypwmfGt81kaoakSOIaS54cQ 5ZX/4aT8jUcqM61qMFVYq3bJ9hzCmecxH0ptmWg8mpGFCE1UpbepYCfCuV3AHbLcD5EY 4bof5+zZP3mG+JRYaWqeLC3BTvN67Of7Ws0Fhmnceh3elddqHGOAjli9vSp11eLD/NsK d7MfVJSyU/Rd1uQNVQlrLVSGk/WvC/pvigQvBqjHZWwqKBXVbmRLYO7j9bgNtt06mp15 KJelIWdBflclyqyTfuajHjidK3zGYOafKkzGHfmuOSeVk+f+XPiat5y84l1e9WKGS0GO P/FQ== X-Received: by 10.66.97.106 with SMTP id dz10mr48593344pab.66.1425310495910; Mon, 02 Mar 2015 07:34:55 -0800 (PST) MIME-Version: 1.0 In-Reply-To: <8A3659BC-9100-4A3A-9117-47227B3D290B@gmail.com> References: <33677AE8-B2FA-49F9-9304-C8D93784255D@gmail.com> <8A3659BC-9100-4A3A-9117-47227B3D290B@gmail.com> From: Ian Kelly Date: Mon, 2 Mar 2015 08:34:15 -0700 Subject: Re: Design thought for callbacks To: Python Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.19 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: 83 NNTP-Posting-Host: 2001:888:2000:d::a6 X-Trace: 1425310505 news.xs4all.nl 2837 [2001:888:2000:d::a6]:59281 X-Complaints-To: abuse@xs4all.nl Xref: csiph.com comp.lang.python:86750 On Mon, Mar 2, 2015 at 4:04 AM, Cem Karan wrote: > On Feb 26, 2015, at 2:54 PM, Ian Kelly wrote: >> On Feb 26, 2015 4:00 AM, "Cem Karan" wrote: >> > >> > >> > On Feb 26, 2015, at 12:36 AM, Gregory Ewing wrote: >> > >> > > Cem Karan wrote: >> > >> I think I see what you're talking about now. Does WeakMethod >> > >> (https://docs.python.org/3/library/weakref.html#weakref.WeakMethod)= solve >> > >> this problem? >> > > >> > > Yes, that looks like it would work. >> > >> > >> > Cool! >> >> Sometimes I wonder whether anybody reads my posts. I suggested a solutio= n involving WeakMethod four days ago that additionally extends the concept = to non-method callbacks (requiring a small amount of extra effort from the = client in those cases, but I think that is unavoidable. There is no way tha= t the framework can determine the appropriate lifetime for a closure-based = callback.) > > I apologize about taking so long to reply to everyone's posts, but I've b= een busy at home. > > Ian, it took me a while to do some research to understand WHY what you we= re suggesting was important; you're right about storing the object as well = as the method/function separately, but I think that WeakMethod might solve = that completely, correct? Are there any cases where WeakMethod wouldn't wo= rk? WeakMethod only works for bound method objects. If you pass it a non-method function, you'll get a TypeError: >>> from weakref import WeakMethod >>> WeakMethod(lambda: None) Traceback (most recent call last): File "", line 1, in File "/usr/lib/python3.4/weakref.py", line 49, in __new__ .format(type(meth))) from None TypeError: argument should be a bound method, not This check uses duck typing, so you could perhaps write a method-like class with __self__ and __func__ attributes and pass that to the WeakMethod constructor in the case of non-methods. There's a bigger issue with this however, which is that WeakMethod works by keeping weak references to *both* the object and the function, meaning that as soon as the function has no other references, the WeakMethod expires even if the object is still alive. This isn't a problem for methods because it's the transience of the method object, not the underlying function, that WeakMethod seeks to work around. But it doesn't by itself do anything to solve the problem of closures or lambdas that may themselves be transient. Revisiting the implementation I suggested previously, I want to make a correction. This would be better solved with a WeakValueDictionary: class Listenable: def __init__(self): self._callbacks =3D weakref.WeakValueDictionary() def listen(self, callback, owner=3DNone): if owner is None: if isinstance(callback, types.MethodType): owner =3D weakref.WeakMethod(callback) else: owner =3D callback # TODO: Should anything happen if the callback is already in the di= ct? self._callbacks[callback] =3D owner def do_callbacks(self, message): for callback in self._callbacks.keys(): callback(message) This approach has two benefits over the previous one: it simplifies the callback management a bit, and it avoids making the assumption that the owner is hashable (it assumes instead that the callback is hashable, but I think that's reasonable).