On Nov 19, 2015 20:48, "Chris Angelico" <ros...@gmail.com> wrote: > > On Fri, Nov 20, 2015 at 5:42 AM, Ian Kelly <ian.g.ke...@gmail.com> wrote: > > BartC on the other hand is just complaining about an aspect of Python > > that is legitimately controversial. > > IMO it's controversial mainly because there's an easy and obvious > syntax for early binding, but late binding doesn't have syntactic > support, and all the options are imperfect. Consider: > > def late1(x=None): > """Terse but buggy""" > do_stuff_with(x or []) > > def late2(x=None): > """Not bad but has an extra line for each default. Also, can't take None.""" > if x is None: x = [] > do_stuff_with(x) > > _SENTINEL = object() > def late3(x=_SENTINEL): > """Has the same global-pollution problem you get when you > try to construct early binding from late; you can share the > sentinel among multiple args, even multiple funcs, though""" > if x is _SENTINEL: x = [] > do_stuff_with(x) > > def late4(x=object()): > """Depends on its own name remaining bound in its scope, > and will break if you change argument order""" > if x is late4.__defaults__[0]: x = [] > do_stuff_with(x) > > And there might be other techniques, too. They all share one important > flaw, too: When you ask for help about the function, you can't see > what the default really is. All you see is: > > late1(x=None) > late3(x=<object object at 0x7f7a80b3a080>) > > In the first two cases, it's not too bad; you can specify a timeout as > either a number or as None, and if it's None (the default), the > timeout is three times the currently estimated round trip time. No > problem. But how would you implement something like next() in pure > Python? If you provide _any second argument at all_, it returns that > instead of raising StopIteration. The ONLY way that I can think of is > to use *args notation: > > def next(iterator, *default): > try: > return iterator.__next__() > except StopIteration: > if default: return default[0] > raise > > And while that isn't TOO bad for just one argument, it scales poorly: > > def foo(fixed_arg, *args): > """Foo the fixed_arg against the spam mapping, the > ham sequence, and the jam file.""" > args.reverse() > spam = args.pop() if args else {} > ham = args.pop() if args else [] > jam = args.pop() if args else open("jam.txt") > > Suppose, instead, that Python had a syntax like this: > > def foo(fixed_arg, spam=>{}, ham=>[], jam=>open("jam.txt")): > """Foo the fixed_arg against the spam mapping, the > ham sequence, and the jam file.""" > > 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. > > Would this satisfy the people who get confused about "=[]"? > > ChrisA
Rather than a dedicated syntax, might this be something that could be handled by a built-in decorator? Maybe something like: @late_binding def myfunc(x, y=[]): @late_binding('z') def myfunc(x, y=expensive(), z=[]): M = 5 @late_binding('z', vars_early=True) def myfunc(x, y=expensive(), z=list(range(n))): The big advantage, as shown in the last example, is that it could be possible to specify early our late binding for variables as well. -- https://mail.python.org/mailman/listinfo/python-list