On Tue, Nov 24, 2015 at 3:40 AM, Ian Kelly <ian.g.ke...@gmail.com> wrote:
> On Mon, Nov 23, 2015 at 1:23 AM, Chris Angelico <ros...@gmail.com> wrote:
>> 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.
>
> You should be able to get the correct globals from f.__globals__.
>
> For locals, the decorator might capture the locals of the previous
> stack frame at the moment the decorator was called, but that's
> potentially a pretty heavy thing to be retaining for this purpose; the
> definition of a @latearg function would indefinitely keep a reference
> to every single object that was bound to a variable in that scope, not
> just the things it needs. For better specificity you could parse the
> expression and then just grab the names that it uses. Even so, this
> would still act somewhat like early binding in that it would reference
> the local variables at the time of definition rather than evaluation.
>
> Nonlocals? Just forget about it.

And since nonlocals are fundamentally unsolvable, I took the simpler
option and just used an actual lambda function.

>> 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"
>
> A parameter annotation could be used in conjunction with the decorator.
>
> @latearg
> def x(y: latearg = lambda: []):
>     ...
>
> But that's even more verbose. In the simple case where all the
> defaults should be late, one could have something like:
>
> @latearg('*')
> def x(y=lambda: []):
>     ...
>
> The argument could be generalized to pass a set of parameter names as
> an alternative to the annotation.

Yeah, that might help. With real compiler support, both of these could
be solved:

def x(y=>[]):
    ...

The displayed default could be "=>[]", exactly the way it's seen in
the source, and the run-time would know exactly which args were
flagged this way. Plus, it could potentially use a single closure to
evaluate all the arguments.

>> 3) Keyword args aren't handled - they're passed through as-is (and
>> keyword-only arg defaults aren't rendered)
>
> I would expect that Python 3 Signature objects would make this a lot
> simpler to handle.

Maybe. It's still going to be pretty complicated. I could easily
handle keyword-only arguments, but recognizing that something in **kw
is replacing something in *a is a bit harder. Expansion invited.

This is reminiscent of the manual "yield from" implementation in PEP
380. It looks simple enough, until you start writing in all the corner
cases.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list

Reply via email to