Path: csiph.com!fu-berlin.de!uni-berlin.de!not-for-mail From: Chris Angelico Newsgroups: comp.lang.python Subject: Re: Late-binding of function defaults (was Re: What is a function parameter =[] for?) Date: Mon, 23 Nov 2015 19:23:54 +1100 Lines: 93 Message-ID: References: Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 X-Trace: news.uni-berlin.de cwXJXsgjCQ+lxBW1xhr59AZ26QEYGdopa366BV2CcUsA== 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; 'args': 0.04; 'compiler': 0.05; 'defaults': 0.05; 'modified': 0.05; 'used.': 0.05; '[],': 0.07; 'executable': 0.07; 'expressions': 0.07; 'cc:addr:python- list': 0.09; '[])': 0.09; 'arg': 0.09; 'syntax': 0.13; 'def': 0.13; 'properly': 0.15; 'subject: \n ': 0.15; '"""an': 0.16; '1",': 0.16; '2")': 0.16; '2",': 0.16; 'as-is': 0.16; 'binding,': 0.16; 'cc:name:python': 0.16; 'closures,': 0.16; 'defs': 0.16; 'discarded': 0.16; 'from:addr:rosuav': 0.16; 'from:name:chris angelico': 0.16; 'interest,': 0.16; 'lambda': 0.16; 'received:io': 0.16; 'received:psf.io': 0.16; 'scope.': 0.16; 'subject:?)': 0.16; 'verbose': 0.16; 'wrote:': 0.16; 'implementing': 0.18; 'nested': 0.18; '2015': 0.20; 'cc:2**0': 0.20; 'cc:addr:python.org': 0.20; 'purposes': 0.20; '%s"': 0.22; "aren't": 0.22; 'arguments': 0.22; 'clock': 0.22; 'decorator': 0.22; 'function,': 0.22; 'parameter': 0.22; 'recognize': 0.22; 'am,': 0.23; 'bit': 0.23; 'import': 0.24; '(this': 0.24; 'header:In-Reply-To:1': 0.24; 'chris': 0.26; 'fri,': 0.27; 'message-id:@mail.gmail.com': 0.27; 'correct': 0.28; 'function': 0.28; 'identifies': 0.29; 'mode.': 0.29; 'code:': 0.29; 'handled': 0.29; 'objects': 0.29; 'code': 0.30; 'checks': 0.30; "i'd": 0.31; 'fixed': 0.31; "can't": 0.32; '"the': 0.32; 'skip:_ 10': 0.32; 'implement': 0.32; 'expensive': 0.32; 'help,': 0.32; 'point': 0.33; 'class': 0.33; 'instead,': 0.33; 'shorter': 0.33; 'correctly': 0.34; 'definition': 0.34; 'skip:d 20': 0.34; 'received:google.com': 0.35; 'ones': 0.35; 'could': 0.35; 'text': 0.35; 'execution': 0.35; 'nov': 0.35; 'text.': 0.35; 'something': 0.35; 'but': 0.36; 'instead': 0.36; 'received:209.85': 0.36; '(and': 0.36; 'keyword': 0.36; 'subject:: ': 0.37; 'received:209.85.213': 0.37; 'things': 0.38; 'late': 0.38; "won't": 0.38; 'received:209': 0.38; 'does': 0.39; 'subject:-': 0.39; 'rather': 0.39; 'where': 0.40; 'skip:u 10': 0.61; 'body': 0.61; 'show': 0.62; 'within': 0.64; '20,': 0.66; "they're": 0.66; 'cut': 0.67; '"here\'s': 0.84; '**kw)': 0.84; 'chrisa': 0.84; 'decorator.': 0.84; 'to:none': 0.91 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=mime-version:in-reply-to:references:date:message-id:subject:from:cc :content-type; bh=eErHUF6yzxJn2FbBeryNwOiwyBqvH4stXfRKumuguR0=; b=bUqwGaMAqATC8pvvozKl+nkYfNYjCkASieQ6EmwRAEccVHL0rCjab+d8F7B1S2jUQf ZsfA2OWRoj+oZtdRfM6ZGzjzz2ZnJ6xi4TNqLS1VQBULWTI1iouidX1St9Ns/f0PLmbl gbsYOiNDE+B6Z1//GUr6wgZ3Qlqm88yTdbQCJYh0bmHbDGvVorqlPkiAkU6qqlmDV0Ds b6Di1rgG0li8KyWVxy0OMeEjTLF6AlRXqWvprW+Q6ycwagjRooGT8WOHZOAvwH7WkRHE eF/JA2jf/+UbodcPyRXHB/PnI63Uihb5S/otcJ0fZz9aG+TgM08eS3/1ahBbelxrbUzY BjvQ== X-Received: by 10.50.30.6 with SMTP id o6mr11605833igh.94.1448267034915; Mon, 23 Nov 2015 00:23:54 -0800 (PST) In-Reply-To: X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.20+ Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Xref: csiph.com comp.lang.python:99258 On Fri, Nov 20, 2015 at 6:46 AM, Chris Angelico wrote: > The expressions would be evaluated as closures, using the same scope > that the function's own definition used. (This won't keep things alive > unnecessarily, as the function's body will be nested within that same > scope anyway.) Bikeshed the syntax all you like, but this would be > something to point people to: "here's how to get late-binding > semantics". For the purposes of documentation, the exact text of the > parameter definition could be retained, and like docstrings, they > could be discarded in -OO mode. Just out of interest, I had a shot at implementing this with a decorator. Here's the code: # -- cut -- import functools import time class lb: def __repr__(self): return "" def latearg(f): tot_args = f.__code__.co_argcount min_args = tot_args - len(f.__defaults__) defs = f.__defaults__ # With compiler help, we could get the original text as well as something # executable that works in the correct scope. Without compiler help, we # either use a lambda function, or an exec/eval monstrosity that can't use # the scope of its notional definition (since its *actual* definition will # be inside this decorator). Instead, just show a fixed bit of text. f.__defaults__ = tuple(lb() if callable(arg) else arg for arg in defs) @functools.wraps(f) def inner(*a,**kw): if len(a) < min_args: return f(*a, **kw) # Will trigger TypeError if len(a) < tot_args: more_args = defs[len(a)-tot_args:] a += tuple(arg() if callable(arg) else arg for arg in more_args) return f(*a,**kw) return inner def sleeper(tm): """An expensive function.""" t = time.monotonic() time.sleep(tm) return time.monotonic() - t - tm seen_args = [] @latearg def foo(spam, ham=lambda: [], val=lambda: sleeper(0.5)): print("%s: Ham %X with sleeper %s" % (spam, id(ham), val)) seen_args.append(ham) # Keep all ham objects alive so IDs are unique @latearg def x(y=lambda: []): y.append(1) return y print("Starting!") foo("one-arg 1") foo("one-arg 2") foo("two-arg 1", []) foo("two-arg 2", []) foo("tri-arg 1", [], 0.0) foo("tri-arg 2", [], 0.0) print(x()) print(x()) print(x()) print(x([2])) print(x([3])) print(x([4])) print("Done!") # -- cut -- This does implement late binding, but: 1) The adornment is the rather verbose "lambda:", where I'd much rather have something shorter 2) Since there's no way to recognize "the ones that were adorned", the decorator checks for "anything callable" 3) Keyword args aren't handled - they're passed through as-is (and keyword-only arg defaults aren't rendered) 4) As commented, the help text can't pick up the text of the function But it does manage to render args at execution time, and the help() for the function identifies the individual arguments correctly (thanks to functools.wraps and the modified defaults - though this implementation is a little unfriendly, mangling the original function defaults instead of properly wrapping). Clock this one up as "useless code that was fun to write". ChrisA