+1 on adding keyword arguments to built-in methods and functions where they
would help readability, e.g str.find(c, start=23), even if this happens in a
ad-hoc fashion.
+0 on forcing *all* built-in methods and functions to be updated to take
keyword arguments out of a sense of purity, e.g. ord(char='c').
I think that "all built-ins should take keywords, so as to minimise the
difference between them and pure-Python functions" is an admirable ideal. But
it is an ideal, a nice-to-have rather than a must-have.
--
Steven
_______________________________________________
Python-ideas mailing list
Python...@python.org
http://mail.python.org/mailman/listinfo/python-ideas
Indeed, this is the approach we have taken to date. For example,
str.split() recently gained keyword support for 3.3 because
"text.split(maxsplit=1)" is less cryptic than "text.split(None, 1)".
It makes the most sense when at least one of the following holds:
- the second argument accepts a number that is unclear if you're not
familiar with the full function signature
- the earlier arguments have sensible default values that you'd prefer
not to override
So +1 on declaring "make X support keyword arguments"
non-controversial for multi-argument functions, +0 on also doing so
for single argument functions, but -0 on attempting to boil the ocean
and fix them wholesale.
Cheers,
Nick.
--
Nick Coghlan | ncog...@gmail.com | Brisbane, Australia
Hm. I think for many (most?) 1-arg and selected 2-arg functions (and
rarely 3+-arg functions) this would reduce readability, as the example
of ord(char=x) showed.
I would actually like to see a syntactic feature to state that an
argument *cannot* be given as a keyword argument (just as we already
added syntax to state that it *must* be a keyword).
One area where I think adding keyword args is outright wrong: Methods
of built-in types or ABCs and that are overridable. E.g. consider the
pop() method on dict. Since the argument name is currently
undocumented, if someone subclasses dict and overrides this method, or
if they create another mutable mapping class that tries to emulate
dict using duck typing, it doesn't matter what the argument name is --
all the callers (expecting a dict, a dict subclass, or a dict-like
duck) will be using positional arguments in the call. But if we were
to document the argument names for pop(), and users started to use
these, then most dict sublcasses and ducks would suddenly be broken
(except if by luck they happened to pick the same name).
--
--Guido van Rossum (python.org/~guido)
There was a discussion about this on this list in 2007. I wrote some
decorators to implement it this functionality. Here's one at
http://code.activestate.com/recipes/521874-functions-with-positional-only-arguments/?in=user-4059385
(note that it didn't attract a lot of attention !). The recipe also
refers to the original discussion.
--
Arnaud
I've written such decorators too, but they've got quite a bit of overhead...
--Guido van Rossum (sent from Android phone)
So something like:
def ord(char, ?):
def split(self, char, ?, count)
def canary(breed, ?, color, wingspan, *, name)
~Ethan~
> I've written such decorators too, but they've got quite a bit of overhead...
The one in the above recipe (which is for 2.X) doesn't incur any
runtime overhead - although it is a bit hackish as it changes the
'co_varnames' attribute of the function's code object.
So it's not written in Python -- it uses CPython specific hacks.
--
--Guido van Rossum (python.org/~guido)
I've written such decorators too, but they've got quite a bit of overhead...
I just want to remain realistic and acknowledge that positional
arguments have their place.
> if we go that route, could it be possible to implement range([start=0, ]
> stop[, step=1]) such that they are positional only but mutliple arguments
> are treated different than strictly sequential without writing conditional
> code in Python to figure out each one's meaning at runtime.
Eew, I don't think this pattern is useful enough to support in syntax,
even if one of the most popular builtins (but only one!) uses it.
> speaking of range... I think start and stop are plenty obvious, but I'd like
> to allow step to be specified as a keyword.
That's fine, range() is not overloadable anyway.
--
--Guido van Rossum (python.org/~guido)
I think this is what we need. I see the problem as being that a) C and
Python functions work differently, and b) the doc does not -- and should
not -- specify the implementation. One solution is to make all C
functions work like Python functions. The other is to allow Python
functions to work like C functions. Given the reasonable opposition to
the first, we need the second.
> So something like:
>
> def ord(char, ?):
>
> def split(self, char, ?, count)
>
> def canary(breed, ?, color, wingspan, *, name)
That is probably better than using '$' or directly tagging the names.
--
Terry Jan Reedy
On Fri, Mar 2, 2012 at 2:01 PM, Gregory P. Smith <gr...@krypto.org> wrote:I just want to remain realistic and acknowledge that positional
>
> On Fri, Mar 2, 2012 at 12:00 PM, Guido van Rossum <gu...@python.org> wrote:
>>
>> I've written such decorators too, but they've got quite a bit of
>> overhead...
>
> yeah those fall into the gross hacks I alluded to in my original post. ;)
>
> I intentionally decided to leave out discussion of "should we allow
> positional-only arguments to be declared in Python" but it is a valid
> discussion and thing to consider...
arguments have their place.
> if we go that route, could it be possible to implement range([start=0, ]Eew, I don't think this pattern is useful enough to support in syntax,
> stop[, step=1]) such that they are positional only but mutliple arguments
> are treated different than strictly sequential without writing conditional
> code in Python to figure out each one's meaning at runtime.
even if one of the most popular builtins (but only one!) uses it.
I chose '?' because it has some similarity to an incompletely-drawn 'p',
and also because it suggests a sort of vagueness, as in not being able
to specify the name of the argument.
I do not know if it is the best possible way, and am looking forward to
other ideas.
~Ethan~
I'd rather not start using a new punctuation character for this one
very limited purpose; it might prevent us from using ? for some other
more generic purpose in the future.
Alternative proposal: how about using '/' ? It's kind of the opposite
of '*' which means "keyword argument", and '/' is not a new character.
--
--Guido van Rossum (python.org/~guido)
How about ';'? Is it possible to re-use it in this context?
def (a; b, *, c)
def (; b)
-
Yury
P.S. Sometimes I feel nostalgic for the moratorium...
Hmm -- not sure that is obvious enough. Also, your second example
doesn't need the semi-colon at all.
~Ethan~
So it would look like:
def ord(char, /):
def split(self, char, /, count)
def canary(breed, /, color, wingspan, *, name)
I think I like that better -- it stands out, and it looks like a barrier
between the positional-only and the positional-keyword arguments.
~Ethan~
Yeah, on reflection, I'm actually -0 on adding keyword arg support to
1-arg functions.
> I would actually like to see a syntactic feature to state that an
> argument *cannot* be given as a keyword argument (just as we already
> added syntax to state that it *must* be a keyword).
I currently write such code as:
def f(*args):
arg1, arg2, arg3 = args
This gives rubbish error messages when the caller makes a mistake, but it works.
The obvious syntactic alternative is allowing tuple expansion
specifically for *args:
def f(*(arg1, arg2, arg3)):
pass
Then the interpreter would have enough info to still generate nice
error messages, and we don't have to invent much in the way of new
syntax.
> One area where I think adding keyword args is outright wrong: Methods
> of built-in types or ABCs and that are overridable. E.g. consider the
> pop() method on dict. Since the argument name is currently
> undocumented, if someone subclasses dict and overrides this method, or
> if they create another mutable mapping class that tries to emulate
> dict using duck typing, it doesn't matter what the argument name is --
> all the callers (expecting a dict, a dict subclass, or a dict-like
> duck) will be using positional arguments in the call. But if we were
> to document the argument names for pop(), and users started to use
> these, then most dict sublcasses and ducks would suddenly be broken
> (except if by luck they happened to pick the same name).
Good point.
The other use case is APIs like the dict constructor and dict.update
which are designed to accept arbitrary keyword arguments, so you don't
want to reserve particular names in the calling argument namespace for
your positional arguments.
Cheers,
Nick.
--
Nick Coghlan | ncog...@gmail.com | Brisbane, Australia
> Alternative proposal: how about using '/' ? It's kind of the opposite
> of '*' which means "keyword argument", and '/' is not a new character.
It took me a moment to get the pun on div / being the inverse of mul *.
I like it. Very clever -- and memorable!
--
Terry Jan Reedy
Kinda incongruous with PEP 3113 though.
- Chris
The problem with that is we then have '*' doing double duty as both
tuple unpacking and keyword-only in the function signature:
def herd(*(size, location), *, breed)
~Ethan~
It's not `def herd(*args, *, breed)`, so I don't see why it would be
`def herd(*(size, location), *, breed)`.
I think Nick's syntax is the right one, although adding the feature to
Python is probably not a good idea.
Mike
speaking of range... I think start and stop are plenty obvious, but I'd like to allow step to be specified as a keyword.
Urrggg, ugly and hard to read. Imagine, if you will:
def spam(x, /, y, /, z, /, a=2/3, /):
...
Placing the tag after the argument as an extra parameter is not the right
approach in my opinion. It's excessively verbose, and it puts the tag in the
wrong place: as you read from left-to-right, you see "normal argument, no,
wait, it's positional only". The tag should prefix the name.
With keyworld-only arguments, the * parameter is special because it flags a
point in the parameter list, not an individual parameter: you read "normal
arg, normal arg, start keyword-only args, keyword-only arg, ...".
I believe that the right place to tag the parameter is in the parameter
itself, not by adding an extra parameter after it. Hence, something like this:
def spam(~x, ~y, ~z, ~a=2/3):
...
where ~name means that name cannot be specified by keyword. I read it as "not
name", as in, the caller can't use the name.
Or if you prefer Guido's pun:
def spam(/x, /y, /z, /a=2/3):
...
Much less line-noise than spam(x, /, y, /, z, /, a=2/3, /).
Personally, I think this is somewhat of an anti-feature. Keyword arguments are
a Good Thing, and while I don't believe it is good enough to *force* all C
functions to support them, I certainly don't want to discourage Python
functions from supporting them.
--
Steven
>> if we go that route, could it be possible to implement range([start=0, ]
>> stop[, step=1]) such that they are positional only but mutliple arguments
>> are treated different than strictly sequential without writing conditional
>> code in Python to figure out each one's meaning at runtime.
>
> Eew, I don't think this pattern is useful enough to support in syntax,
> even if one of the most popular builtins (but only one!) uses it.
I read this at first that you didn't approve of the range API.
I agree that the API is too specialized to take syntactical support, but I'd
just like to put my hand up and say I like range's API and have very
occasionally used it for my own functions. I see no point in having special
syntax for it: this is a specialized enough case that I don't mind handling it
manually.
--
Steven
I have frequently felt that range(start=5, stop=27, step=2) reads better than
range(5, 27, 2), but not better *enough* to bother requesting a change,
particular given the conservative nature of the Python devs to such minor
interface changes (and rightly so).
If it came to a vote, I'd vote 0 on the status quo, +0 to allow all three
arguments to be optionally given by keyword, and -1 for singling step out as
the only one that can be a keyword. That's just silly (sorry Gregory!) -- can
you imagine explaining to a beginner why they can write range(0, 50, 2) or
range(0, 50, step=2) but not range(0, stop=50, step=2)?
--
Steven
That can't be right -- if a parameter is positional, surely all
parameters before it are also positional, so it would be redundant to
have to mark all of them up. Also ~name looks too much like an
expression and /name looks just weird (I think DOS command line flags
used to look like this).
> Personally, I think this is somewhat of an anti-feature. Keyword arguments
> are a Good Thing, and while I don't believe it is good enough to *force* all
> C functions to support them, I certainly don't want to discourage Python
> functions from supporting them.
And yet people invent decorators and other hacks to insist on
positional parameters all the time. You *can* have Too Much of a Good
Thing, and for readability it's better if calls are consistent. If
most calls to a function use positional arguments (at least for the
first N positions), it's better to force *all* calls to use positional
arguments 1-N: the reader may be unfamiliar with the parameter names.
Also remember the subclassing issue I brought up before.
That said, I can't come up with a syntax that I really like. Here's my
best attempt, but I'm at most -0 on it: Have a stand-alone '/'
indicate "all parameters to my left must be positional", just like a
stand-alone '*' means "all parameters to my right must be keywords".
If there's no stand-alone '*' it is assumed to be all the way on the
right; so if there's no '/' it is assumed to be all the way on the
left. The following should all be valid:
def foo(/, a, b): ... # edge case, same as def foo(a, b): ...
def foo(a, b, /): ... # all positional
def foo(a, b=1, /): ... # all positional, b optional
def foo(a, b, /, c, d): ... # a, b positional; c, d required and
either positional or keyword
def foo(a, b, /, c, d=1): ... # a, b positional; c required, d
optional; c, d either positional or keyword
def foo(a, b, /, c, *, d): ... # a, b positional; c required and
either positional or keyword; d required keyword
def foo(a, b=1, /, c=1, *, d=1): ... # same, but b, c, d optional
That about covers it. But agreed it's no thing of beauty, so let's abandon it.
--
--Guido van Rossum (python.org/~guido)
I don't believe that's what the proposal is anyway. Note Ethan's
"barrier" comment.
> It's excessively verbose, and it puts the tag in the
> wrong place: as you read from left-to-right, you see "normal argument, no,
> wait, it's positional only". The tag should prefix the name.
>
> With keyworld-only arguments, the * parameter is special because it flags a
> point in the parameter list, not an individual parameter: you read "normal
> arg, normal arg, start keyword-only args, keyword-only arg, ...".
That's in the same vein as what I understand the proposal to be. "/"
would flag "end of positional-only args"; there's effectively an
implicit leading "/" if you don't use one in a function's definition.
<snip>
> Personally, I think this is somewhat of an anti-feature. Keyword arguments
> are a Good Thing, and while I don't believe it is good enough to *force* all
> C functions to support them, I certainly don't want to discourage Python
> functions from supporting them.
+1
I can see not modifying every single C implementation as it's for
little gain, and in a bunch of cases concerns 1-or-2-argument
functions which are arguably worth special-casing. But making the
function definition syntax even more complicated (we have annotations
now, remember?) just to allow forcing calls to be (slightly) more
restrictive/opaque? Hard to get on board with that.
Cheers,
Chris
There's no need to explain anything to beginners, they just accept
whatever rules you give them. It's the people who are no longer
beginners but not quite experts you have to deal with. But a true zen
master, even a zen-of-Python master, would just hit them over the head
with a wooden plank. (Seriously, there are plenty of APIs that have
some positional parameters and some keyword parameters, and I see
nothing wrong with that. You seem to be too in love with keyword
parameters to see clearly. :-)
Still, I can't think of a reason why we should upset Raymond over such
a minor thing, so let's forget about "fixing" range. And that's final.
--
--Guido van Rossum (python.org/~guido)
And I was just starting to like it, too. :(
Personally, I don't see it as any uglier than having the lone '*' in the
signature; although, I don't see lone '*'s all that much, whereas the
'/' could be quite prevalent.
Is this something we want? We could have a built-in decorator, like
property or staticmethod, to make the changes for us (each
implementation would have to supply their own, of course):
@positional(2)
def foo(a, b)
Or we could use brackets or more parentheses:
def foo([a, b])
def foo((a, b))
That doesn't seem too bad...
def foo((a, b=1), c=2, *, d=3)
Since tuple-unpacking no longer happens in the definition signature, we
don't need the leading * before the parentheses.
Just my last two cents (unless the idea garners encouragement, of course ;).
~Ethan~
range([start,] stop[, step])
slice([start,] stop[, step])
itertools.islice(iterable, [start,] stop [, step])
random.randrange([start,] stop[, step])
syslog.syslog([priority,] message)
curses.newwin([nlines, ncols,] begin_y, begin_x)
curses.window.addch([y, x,] ch[, attr])
curses.window.addnstr([y, x,] str, n[, attr])
curses.window.addstr([y, x,] str[, attr])
curses.window.chgat([y, x, ] [num,] attr)
curses.window.derwin([nlines, ncols,] begin_y, begin_x)
curses.window.hline([y, x,] ch, n)
curses.window.insch([y, x,] ch[, attr])
curses.window.insnstr([y, x,] str, n [, attr])
curses.window.subpad([nlines, ncols,] begin_y, begin_x)
curses.window.subwin([nlines, ncols,] begin_y, begin_x)
curses.window.vline([y, x,] ch, n)
>> speaking of range... I think start and stop are plenty obvious, but I'd like
>> to allow step to be specified as a keyword.
> That's fine, range() is not overloadable anyway.
There are number of range-like functions in third-party modules.
You can write print(1, 2, 3, end='') but can't write... what?
How about using '**' (and left '/' for some purpose in the future)?
How about lambdas?
foo = lambda a; b: return a
True. OTOH if you decided to put such a decorator in CPython's
standard library (and I'm not talking about this specific
implementation of the decorator), then implementors of other Pythons
would have to provide the same functionality. We would then get the
ability to have positional only arguments free of overhead without
having to make the syntax of function signatures even more complex.
Also, a decorator would be a signal to users that positional only
argument are not often necessary, whereas offering syntactical support
for them may encourage over use of the feature.
--
Arnaud
+1
> Micro rearrangements of the language and a real PITA for folks
> who have to go back-and-forth between different versions of Python.
> So, we should raise the bar to something higher than "I'd like feature
> X"
> and ask for examples of code that would be better or for user requests
> or some actual demonstration of need. ISTM that 20 years of history
> with range() suggests that no one needs this (nor have I seen a need
> in any other language with functions that take a start/stop/step).
On a more general note...
It seems to me that sometimes the writer of functions wish to have more
control of how the function is called, but I think it is better that the
user of a function can select the calling form that perhaps matches the
data and/or style they are using more closely.
I hope in the future that we find ways to simplify function signatures
in a way that make them both easier to use and more efficient for the
function user, rather than continue adding specific little tweaks that
give the function designer more control over how the function user calls
it.
My 2cents,
Ron
I agree with that, but it can still make sense to have positional-only
arguments. For example, we want d.update(self=4) to update the 'self'
key on any Mapping, so the default implementation update on the ABC has
to accept *args, **kwargs and have some code to extract self:
http://hg.python.org/cpython/file/e67b3a9bd2dc/Lib/collections/abc.py#l511
Without this "hack", passing self=4 would give TypeError: got multiple
values for keyword argument 'self'.
It would be so much nicer to be able to declare self and other
positional-only in "def update(self, other=(), **kwargs):"
Regards,
--
Simon Sapin
Um, the function author chooses the signature. If you disagree with
that signature, tough luck.
> I hope in the future that we find ways to simplify function signatures
> in a way that make them both easier to use and more efficient for the
> function user, rather than continue adding specific little tweaks that
> give the function designer more control over how the function user calls
> it.
You seem to forget that API design is an art and that it is the
function author's prerogative to design an API that minimizes mistakes
for all users of the function. Sometimes that includes requiring that
everyone uses positional arguments for a certain situation.
Anyway, I now think that adding a built-in @positional(N) decorator
makes the most sense since it doesn't require changes to the parser.
The built-in can be implemented efficiently. This should be an easy
patch for someone who wants to contribute some C code.
--
--Guido van Rossum (python.org/~guido)
Also, a decorator would be a signal to users that positional only
argument are not often necessary, whereas offering syntactical support
for them may encourage over use of the feature.
> True. OTOH if you decided to put such a decorator in CPython's
> standard library (and I'm not talking about this specific
> implementation of the decorator), then implementors of other Pythons
> would have to provide the same functionality. We would then get the
> ability to have positional only arguments free of overhead without
> having to make the syntax of function signatures even more complex.
>
> Also, a decorator would be a signal to users that positional only
> argument are not often necessary, whereas offering syntactical support
> for them may encourage over use of the feature.
A decorator does not solve the problem of *documenting* position-only
args, unless you propose to put them in the doc also.
--
Terry Jan Reedy
> Anyway, I now think that adding a built-in @positional(N) decorator
> makes the most sense since it doesn't require changes to the parser.
> The built-in can be implemented efficiently. This should be an easy
> patch for someone who wants to contribute some C code.
Would you then be okay with using that in documentation?
@positional(1)
ord(char)
Return the integer code for char
If you prefer that to
ord(char, /)
Return the integer code for char
fine with me. I care more about being able to document existing apis for
C-implemented functions than about being able to limit Python functions
I write. (Of course, being able to make C and Python versions of stdlib
modules match would also be great!) Currently, one may need to
experiment before using name-passing to be sure it will work, which
tends to discourage name-passing of args even when it would be more
readable.
--
Terry Jan Reedy
The @positional(1) form looks like it would be easier to understand if
you aren't familiar with it than the / form.
> I care more about being able to document existing apis for
> C-implemented functions than about being able to limit Python functions I
> write. (Of course, being able to make C and Python versions of stdlib
> modules match would also be great!) Currently, one may need to experiment
> before using name-passing to be sure it will work, which tends to discourage
> name-passing of args even when it would be more readable.
Yeah, so it does make sense to standardize on a solution for this. Let
it be @positional(N). Can you file an issue?
--
--Guido van Rossum (python.org/~guido)
Is the N in positional(N) positional-only itself? ;)
More seriously, is N required or can we omit it when all arguments are
to be positional?
Regards,
--
Simon Sapin
I don't think that is a good idea. We currently put argument default
values in the function signatures in Python syntax, but only because
that also makes sense from a documentation PoV.
We also wouldn't write
@property
name(self)
just because that's (one) way for creating properties from Python.
Georg
(I am -0 on @positional myself: IMO such a completely different way
of declaring positional-only and keyword-only arguments lacks grace.)
-1 from me; too much overlap with **kwargs keyword argument insertion in
calls. We'd have ** in calls for keyword arguments and ** in definitions
for not keyword arguments.
--
Cameron Simpson <c...@zip.com.au> DoD#743
http://www.cskk.ezoshosting.com/cs/
What if there were no hypothetical situations? - Jeff Sauder
"*identifier" is a tuple receiving any excess positional parameters.
"**identifier" is a dictionary receiving any excess keyword arguments.
Parameters after "*" are keyword-only parameters.
? Parameters before "**" are positional-only parameters.
More excitiingly, one could embed a slice in the hypothetical
positional-only syntax to say the 0th, 2nd and 4th parameters are
positional-only. Or an arbitrary sequence!
Hmm, a callable that returns an iterable at function call time!
Sorry, too much coffee...
--
Cameron Simpson <c...@zip.com.au> DoD#743
http://www.cskk.ezoshosting.com/cs/
Give me the luxuries of life and I will willingly do without the necessities.
- Frank Lloyd Wright
Also -0 for the same reason.
~Ethan~
On Sat, 2012-03-03 at 09:54 -0800, Guido van Rossum wrote:
> On Sat, Mar 3, 2012 at 7:20 AM, Ron Adam <ron...@gmail.com> wrote:
> > It seems to me that sometimes the writer of functions wish to have more
> > control of how the function is called, but I think it is better that the
> > user of a function can select the calling form that perhaps matches the
> > data and/or style they are using more closely.
>
> Um, the function author chooses the signature. If you disagree with
> that signature, tough luck.
Yes, except the caller does have the option to use the *args and/or
**kwds and other variations of packing and unpacking when it's
convenient. And yes, I realize it doesn't really change the signature
it self, but it is a different way to spell a function call and
sometimes is very helpful when the data can be matched to the signature.
For example, by not specifying any arguments as keyword only, or
position only, both of the following examples work, and the function
caller has these options.
>>> def foo(a, b):
... return a, b
...
>>> kwds = dict(a=1, b=2)
>>> foo(**kwds)
(1, 2)
>>> args = (1, 2)
>>> foo(*args)
(1, 2)
But by specify an argument as keyword only, then it removes the *args
option.
And also if an argument is specified as position only, then the **kwds
spelling wont work.
I'm not suggesting there isn't sometimes a need for being more specific,
but that quite often it's nicer to let the caller have those options
rather than limit the API too narrowly.
> > I hope in the future that we find ways to simplify function signatures
> > in a way that make them both easier to use and more efficient for the
> > function user, rather than continue adding specific little tweaks that
> > give the function designer more control over how the function user calls
> > it.
>
> You seem to forget that API design is an art and that it is the
> function author's prerogative to design an API that minimizes mistakes
> for all users of the function. Sometimes that includes requiring that
> everyone uses positional arguments for a certain situation.
I was trying to make a more general point which is why I preceded my
comments with, "On a more general note...", which was left out of the
reply.
Yes, it most definitely is an ART to create a good API. And also yes,
sometimes minimizing errors take priority, especially when those errors
can be very costly.
It seems to me, binding an object by name is less likely to be wrong
than binding a object by it's position.
The advantage of 'by position' is that many objects are stored in
ordered lists. ie.. [start, stop, step], [x, y, z]. So it's both
easier and more efficient to use the position rather than a name
especially if the object can be *unpacked directly into the signature.
I just can't think of a good case where I would want to prohibit setting
an argument by name on on purpose. But I suppose if I encountered a
certain error that may have been caught by doing so, I may think about
doing that. <shrug>
Cheers,
Ron
> Anyway, I now think that adding a built-in @positional(N) decorator
> makes the most sense since it doesn't require changes to the parser.
> The built-in can be implemented efficiently. This should be an easy
> patch for someone who wants to contribute some C code.
_______________________________________________
Apparently you skipped part of the thread. <shrug**2>
--
--Guido van Rossum (python.org/~guido)
Those situations are probably very rare. AFAIK we haven't seen anyone
mention a serious use case. I think concerns of built-in functions
shadowed by Python functions or the reverse are mostly academic, since
we don't see anyone complaining about dict-alikes accepting keyword
args.
(besides, what happened to "consenting adults"? :-))
> Anyway, I now think that adding a built-in @positional(N) decorator
> makes the most sense since it doesn't require changes to the parser.
-1 on a built-in for that. The functools module would probably be a
good recipient (assuming the decorator is useful at all, of course).
Regards
Antoine.
No, because the base class's insistence on positional args makes it a
non-starter to use keyword args.
But APIs that are implemented in Python don't have this nudge. Given
that some folks here have expressed a desire to use keyword args
*everywhere*, which I consider going way overboard, as a readability
and consistency advocate I want to be able to remind them strongly in
some cases not to do that.
> (besides, what happened to "consenting adults"? :-))
We used to say that about the lone star feature too. But it's turned
out quite useful, both for readability (require callers to name the
options they're passing in) and for allowing evolution of a signature
by leaving the option to add another positional argument in the
future.
Some folks seem to believe that keywords are always better. I want to
send a strong message that I disagree.
>> Anyway, I now think that adding a built-in @positional(N) decorator
>> makes the most sense since it doesn't require changes to the parser.
>
> -1 on a built-in for that. The functools module would probably be a
> good recipient (assuming the decorator is useful at all, of course).
TBH, I've never gotten the hang of functools. It seems mostly a refuge
for things I don't like; so @positional() doesn't belong there. :-)
--
--Guido van Rossum (python.org/~guido)
I think you're reading too much into what has been a pretty luke-warm response
to Gregory's suggestion.
As far as I can see, I've been the least negative about the idea, and that was
a half-hearted +0. I described it as "nice to have" specifically on the basis
of consistency, to minimize the differences between pure-Python functions and
built-ins.
On reflection, your argument about subclassing built-ins has convinced me to
drop that to a -1: if we were designing Python from scratch, it would be a
nice-to-have for built-ins to take named arguments, but since they don't, it
would be too disruptive to add them en-mass.
I'm still +1 on adding named arguments to built-ins where needed, e.g.
>>> "spam".find('a', end=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: find() takes no keyword arguments
but I hope that would be uncontroversial!
>> (besides, what happened to "consenting adults"? :-))
>
> We used to say that about the lone star feature too. But it's turned
> out quite useful, both for readability (require callers to name the
> options they're passing in) and for allowing evolution of a signature
> by leaving the option to add another positional argument in the
> future.
>
> Some folks seem to believe that keywords are always better. I want to
> send a strong message that I disagree.
Positional-only arguments should be considered a new feature that requires
justification, not just to send a message against mass re-engineering of
built-ins.
Some arguments in favour:
* Consistency with built-ins.
* Functions that take a single argument don't need to be called by keyword.
(But is that a reason to prohibit it?)
* It gives the API developer another choice when designing their function API.
Maybe some people just don't like keyword args.
(But is "I don't like them" a good reason to prohibit them? I'd feel more
positive about this argument if I knew a good use-case for designing a new
function with positional-only arguments.)
Arguments against:
* YAGNI. The subclass issue is hardly new, and as far as I know, has never
been an actual problem in practice. Since people aren't calling subclass
methods using keywords, why try to enforce it?
* It's another thing to learn about functions. "Does this function take
keyword arguments or not?"
So far, I'm +0 on this.
>>> Anyway, I now think that adding a built-in @positional(N) decorator
>>> makes the most sense since it doesn't require changes to the parser.
>> -1 on a built-in for that. The functools module would probably be a
>> good recipient (assuming the decorator is useful at all, of course).
>
> TBH, I've never gotten the hang of functools. It seems mostly a refuge
> for things I don't like; so @positional() doesn't belong there. :-)
What, you don't like @wraps? Astonishing! :-)
--
Steven
How could that even work?
Consider the following subset of the Mapping API:
class C:
@positional(2):
def __init__(self, data=None, **kwds):
self._stored_data = stored = {}
if data is not None:
stored.update(data)
stored.update(kwds)
@positional(2):
def update(self, data=None, **kwds):
stored = self._stored_data
if data is not None:
stored.update(data)
stored.update(kwds)
Without gross hacking of the function internals, there's no way for a
decorator to make the following two calls work properly:
x = C(self=5, data=10)
x.update(self=10, data=5)
Both will complain about duplicate "self" and "data" arguments, unless
the "positional" decorator truly rips the function definition apart
and creates a new one that alters how the interpreter maps arguments
to parameters.
As Simon Sapin pointed out, the most correct way to write such code
currently is to accept *args and unpack it manually, which is indeed
exactly how the Mapping ABC implementation currently works [1]. While
the Mapping implementation doesn't currently use it, one simple way to
write such code is to use a *second* parameter binding step like this:
class C:
def _unpack_args(self, data=None):
return self, data
def __init__(*args, **kwds):
self, data = C._unpack_args(*args)
self._stored_data = stored = {}
if data:
stored.update(data)
stored.update(kwds)
def update(*args, **kwds):
self, data = C._unpack_args(*args)
stored = self._stored_data
if data is not None:
stored.update(data)
stored.update(kwds)
The downside, of course, is that the error messages that come out of
such a binding operation may be rather cryptic (which is why the
Mapping ABC instead uses manual unpacking - so it can generate nice
error messages)
The difficulty of implementing the Mapping ABC correctly in pure
Python is the poster child for why the lack of positional-only
argument syntax is a language wart - we define APIs (in C) that work
that way, which people then have to reconstruct manually in Python.
My proposal is that we simply added a *third* alternative for "*args":
a full function parameter specification to be used to bind the
positional-only arguments.
That is:
1. '*args' collects the additional positional arguments and places
them in a tuple
2. '*' disallows any further positional arguments.
3. '*(SPEC)' binds the additional positional arguments according to
the parameter specification.
In all 3 cases, any subsequent parameter defintions are keyword only.
The one restriction placed on the nested SPEC is that it would only
allow "*args" at the end. The keyword only argument and positional
only argument forms would not be allowed, since they would make no
sense (as all arguments to the inner parameter binding operation are
positional by design).
Then the "_unpack_args" hack above would be unnecessary, and you could
just write:
class C:
def __init__(*(self, data=None), **kwds):
self._stored_data = stored = {}
if data:
stored.update(data)
stored.update(kwds)
def update(*(self, data=None), **kwds):
stored = self._stored_data
if data is not None:
stored.update(data)
stored.update(kwds)
The objection was raised that this runs counter to the philosophy
behind PEP 3113 (which removed tuple unpacking from function
signatures). I disagree:
- this is not tuple unpacking, it is parameter binding
- it does not apply to arbitrary arguments, solely to the "extra
arguments" parameter, which is guaranteed to be a tuple
- it allows positional-only arguments to be clearly expressed in the
function signature, allowing the *interpreter* to deal with the
creation of nice error messages
- it *improves* introspection, since the binding of positional only
arguments is now expressed clearly in the function header (and
associated additional metadata on the function object), rather than
being hidden inside the function implementation
Regards,
Nick.
[1] http://hg.python.org/cpython/file/e67b3a9bd2dc/Lib/collections/abc.py#l511
--
Nick Coghlan | ncog...@gmail.com | Brisbane, Australia
Yep, I missed Nicks message where he points out...
> The other use case is APIs like the dict constructor and dict.update
> which are designed to accept arbitrary keyword arguments, so you don't
> want to reserve particular names in the calling argument namespace for
> your positional arguments.
>>> def dct(a, **kwds):
... return a, kwds
>>> dct(42, a=2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: dct() got multiple values for keyword argument 'a'
Would the positional decorator fix this particular case? It seems like
it would work for forcing an error, but not for multiple values with the
same name.
The way to currently get around this is to use *args along with **kwds.
>>> def dct(*args, **kwds):
... (n,) = args # errors here if incorrect number of args.
... return n, kwds
...
>>> dct(3, n=7, args=42, kwds='ok')
(3, {'args': 42, 'kwds': 'ok', 'n': 7})
The names used with '*' and '**' are already anonymous as far as the foo
signature is concerned, so you can use args or kwds as keywords without
a problem.
I'm not sure what the positional decorator would gains over this.
The other use case mentioned is the one where you point out overriding
an undocumented variable name. Seems like this is a question of weather
or not it is better to make python behavior match the C builtins
behavior, vs making the C builtins behavior match python behavior.
Cheers,
Ron
@wraps is actually quite useful. functools contains other decorators
such as @lru_cache. I think it's the right place for little-used things
like @positional.
Regards
Antoine.
No, because str may be subclassed, so this change would break
backwards compatibility for subclasses that chose a different name
instead of 'end'.
--
--Guido van Rossum (python.org/~guido)
David Townshend wrote:
There are two issues being discussed here:
1. A new syntax for positional-only arguments. I don't really see any good use case for this which can't already be dealt with quite easily using *args. True, it means a bit more work on the documentation, but is it really worth adding new syntax (or even a built-in decorator) just for that?
The problem with *args is that it allows 0-N arguments, when we want, say, 2.
2. How to avoid the problems with name-binding to an intended positional only argument. Once again, this can be dealt with using *args.
Again, the problem is *args accepts a range of possible arguments.
In both cases it would be nice to be able to avoid having to manually parse *args and **kwargs, but I haven't really seen anything better that the status quo for dealing with them. The only way I see this really working is to somehow bind positional-only arguments without binding each them to a specific name, and the only way I can think of to do that is to store them in a tuple. Perhaps, then, the syntax should reflect a C-style array:
# pos(2) indicates 2 positional arguments
def f(pos(2), arg1, *args, **kwargs):
print(pos)
print(arg1)
print(args)
print(kwargs)
Not good. The issue is not restricting the author from binding the positional arguments to names, the issue is restricting the user from binding the arguments to names -- but even then, the user (and the author!) need to have those names apparent.
For example:
str.split(pos(2))
Quick, what should be supplied for the two positional arguments?
We want the arguments bound to names *in the function* -- we don't want the arguments bound to names *in the function call*.
The problem with *args is that it allows 0-N arguments, when we want,
say, 2.
> 2. How to avoid the problems with name-binding to an intended
> positional only argument. Once again, this can be dealt with using *args.
Again, the problem is *args accepts a range of possible arguments.
> In both cases it would be nice to be able to avoid having to manually
> parse *args and **kwargs, but I haven't really seen anything better that
> the status quo for dealing with them. The only way I see this really
> working is to somehow bind positional-only arguments without binding
> each them to a specific name, and the only way I can think of to do that
> is to store them in a tuple. Perhaps, then, the syntax should reflect a
> C-style array:
>
> # pos(2) indicates 2 positional arguments
> def f(pos(2), arg1, *args, **kwargs):
> print(pos)
> print(arg1)
> print(args)
> print(kwargs)
Not good. The issue is not restricting the author from binding the
positional arguments to names, the issue is restricting the user from
binding the arguments to names -- but even then, the user (and the
author!) need to have those names apparent.
For example:
str.split(pos(2))
Quick, what should be supplied for the two positional arguments?
We want the arguments bound to names *in the function* -- we don't want
the arguments bound to names *in the function call*.
~Ethan~
I am very well aware of this (it occurs in two different places in the
NDB library that I've been developing for Google App Engine).
But either I missed some messages in the thread (quite possible) or
you're bringing this up for the first time now -- the @positional
decorator wasn't meant to solve this case (which only occurs when
**kwds is used in this particular way).
*If* you want to solve this I agree that some actual new syntax is
probably needed.
> Both will complain about duplicate "self" and "data" arguments, unless
> the "positional" decorator truly rips the function definition apart
> and creates a new one that alters how the interpreter maps arguments
> to parameters.
>
> As Simon Sapin pointed out, the most correct way to write such code
> currently is to accept *args and unpack it manually, which is indeed
> exactly how the Mapping ABC implementation currently works [1]. While
> the Mapping implementation doesn't currently use it, one simple way to
> write such code is to use a *second* parameter binding step like this:
>
> class C:
>
> def _unpack_args(self, data=None):
> return self, data
>
> def __init__(*args, **kwds):
> self, data = C._unpack_args(*args)
> self._stored_data = stored = {}
> if data:
> stored.update(data)
> stored.update(kwds)
>
> def update(*args, **kwds):
> self, data = C._unpack_args(*args)
> stored = self._stored_data
> if data is not None:
> stored.update(data)
> stored.update(kwds)
Nice; that idiom should be more widely known.
> The downside, of course, is that the error messages that come out of
> such a binding operation may be rather cryptic (which is why the
> Mapping ABC instead uses manual unpacking - so it can generate nice
> error messages)
Still, a naming convention for the helper function can probably make
this fairly painless -- perhaps you'll need a separate helper function
for each API function, named in a systematic fashion.
> The difficulty of implementing the Mapping ABC correctly in pure
> Python is the poster child for why the lack of positional-only
> argument syntax is a language wart - we define APIs (in C) that work
> that way, which people then have to reconstruct manually in Python.
Nobody else seems to have seen the importance of solving *this*
particular issue directly in the function signature -- but I
personally support trying!
> My proposal is that we simply added a *third* alternative for "*args":
> a full function parameter specification to be used to bind the
> positional-only arguments.
>
> That is:
>
> 1. '*args' collects the additional positional arguments and places
> them in a tuple
> 2. '*' disallows any further positional arguments.
> 3. '*(SPEC)' binds the additional positional arguments according to
> the parameter specification.
>
> In all 3 cases, any subsequent parameter definitions are keyword only.
+1. This is certainly the most thorough solution for both problems at
hand (simply requiring some parameters to be positional, and the
specific issue when combining this with **kwds).
> Regards,
> Nick.
>
> [1] http://hg.python.org/cpython/file/e67b3a9bd2dc/Lib/collections/abc.py#l511
--
--Guido van Rossum (python.org/~guido)
Then please consider also re-introducing parameter tuple unpacking,
since that was genuinely useful.
Regards
Antoine.
That's debatable - reread PEP 3113.
I added my +1 to Nick's proposal a little hastily, it should have been
+0. I think that *if* we want to solve this, my '/' solution should
also be on the table. It has the downside of not being obvious, but I
don't think that Nick's proposal is all that obvious either to people
who encounter it for the first time -- you have to combine a bunch of
powerful ideas to "get" it. And the () inside () just *begs* for
arbitrary nesting, which we don't want to reintroduce. We don't want
this:
def foo(*(*(*(a, b), c), d), e): ... :-)
--
--Guido van Rossum (python.org/~guido)
I don't understand -- example of what's allowed and not allowed?
> +1. This is certainly the most thorough solution for both problems at
> hand (simply requiring some parameters to be positional, and the
> specific issue when combining this with **kwds).
So a (more or less) complete rundown would look like this:
def foo(*(a, b)): ... # all positional
def foo(*(a, b=1)): ... # all positional, b optional
def foo(*(a, b), c, d): ... # a, b positional; c, d required and keyword
def foo(*(a, b), c, d=1): ... # a, b positional; c required, d optional;
c & d keyword
def foo(*(a, b=1), c=1, d=1): ... # same, but b, c, d optional
If I understand correctly, there is no way to have positional-only,
position-or-keyword, and keyword-only in the same signature?
It may have been useful, but my understanding is that it was removed
because the complications in implementing it were greater, particularly
where introspection was concerned.
~Ethan~
Heh. If that's true, my '/' proposal wins:
def foo(pos_only, /, pos_or_kw, *, kw_only): ...
Defaults can be added to taste.
The restrictions on args-without-defaults being unable to follow
args-with-defaults may need to be revisited so we can combine optional
positional arguments with required keyword arguments, if we want to
support that.
Nevertheless all this is pretty esoteric and I wouldn't cry if it
wasn't added. There exist solutions for the Mapping-API problem, and a
@positional decorator would cover most other cases.
--
--Guido van Rossum (python.org/~guido)
> Yeah, so it does make sense to standardize on a solution for this
Agreed. There are actually two issues.
Doc: indicate intent, regardless of how enforced in code.
Code: indicate intent to interpreter so it enforces intent rather than
programmer doing do with *args, defaults if any, and error messages.
> Let it be @positional(N).
You seem to have backed off on that. I would like a solution for the
docs that Georg can tolerate.
> Can you file an issue?
When you have settled on one thing for at least a day ;-).
Until then, I think it is better to keep discussion in one place, which
is here.
---
The pos(n) idea does not work because position-only args may still have
defaults. For instance, range() takes 1 to 3 args. That proposal did
give me this idea: tag positional names with their index. In a sense,
the index *is* the internal name while apparent alphabetic name is
suggestive for human understanding.
For doc purposes, the tag could be either a prefix or suffix. Either
way, it would be a convention that does not conflict with any stdlib
names that I know of.
range(start_0 = 0, stop_1, step_2 = 1)
Retern ...
range(0_start = 0, 1_stop, 2_step = 1)
Return ...
For Python code, I presume the prefix form would be rejected by the
lexer. A possibility would be 'mangled' dunder names in the signature,
such as __0_start__, which would be stripped to 'start' for use in the code.
If this idea makes '/' look better, fine with me.
--
Terry Jan Reedy
def foo([self], a, b, *args, **kwds):
...
The square brackets are meant to suggest that the name is
something only of interest to the implementation of the function,
and not to be taken as part of the API.
--
Greg
Or _name, as for "private" class and module members.
Extend this for function arguments:
"""
However, there is a convention that is followed by most Python code: a
name prefixed with an underscore (e.g. _spam) should be treated as a
non-public part of the API (whether it is a function, a method or a data
member). It should be considered an implementation detail and subject to
change without notice.
Please do not give syntactic meaning to [parameter], unless it matches the
existing convention for optional parameters.
Besides, positional-only arguments are not only of interest to the
implementation, they are part of the API.
> Or _name, as for "private" class and module members.
In my own functions, I use _name for private implementation arguments, and
usually explicitly document that callers should not rely on them. In the
implementation, sometimes I need to use that private argument, and I always do
so by name so that it stands out that I'm using a special argument, e.g.
something like:
def func(spam, ham, cheese, _name=eggs):
if condition:
x = func(spam, ham, cheese, _name=beans)
...
If you also overload _name to also mean "positional only", I would have to write
x = func(spam, ham, cheese, beans)
which looks like a "normal" argument.
And as already mentioned, the use of _name to mean positional-only and private
would clash with functions which want public positional-only.
--
Steven
Now that I understand that / will only appear in at most one place, like *
(and not following each and every positional-only arg) this is the nicest
syntax I've seen yet.
If we have to have this feature, +1 on this syntax.
I'm still only +0 on the feature itself.
--
Steven
Guido van Rossum wrote:Now that I understand that / will only appear in at most one place, like * (and not following each and every positional-only arg) this is the nicest syntax I've seen yet.
On Sun, Mar 4, 2012 at 9:20 AM, Ethan Furman <et...@stoneleaf.us> wrote:
If I understand correctly, there is no way to have positional-only,
position-or-keyword, and keyword-only in the same signature?
Heh. If that's true, my '/' proposal wins:
def foo(pos_only, /, pos_or_kw, *, kw_only): ...
Defaults can be added to taste.
If we have to have this feature, +1 on this syntax.
I'm still only +0 on the feature itself.
--
Steven
_______________________________________________
Python-ideas mailing list
Python...@python.org
http://mail.python.org/mailman/listinfo/python-ideas
http://www.voidspace.org.uk/
May you do good and not evil
May you find forgiveness for yourself and forgive others
May you share freely, never taking more than you give.
-- the sqlite blessing http://www.sqlite.org/different.html
I think that's a genuine problem in theory. But is it a problem in practice?
Since find('a', end=1) doesn't currently work, there won't be any code using
it in practice. Even if a subclass looks like this:
class MyString(str):
def find(self, substring, beginning=0, last=None):
...
internally MyString.find must be using positional arguments if it calls
str.find, because keyword arguments don't currently work. So this suggested
change will not break existing code.
I can see one other objection to the change: if str.find accepts keywords, and
MyString.find accepts *different* keywords, that is a violation of the Liskov
Substitution Principle. Those who care about this would feel obliged to fix
their code to match the argument names used by str.find, so if you call that
mismatch "breaking backwards compatibility", I accept that.
[Aside: in my experience, most programmers are unaware of Liskov, and
accidentally or deliberately violate it frequently.]
But given that str.find has been documented as "S.find(sub[, start[, end]])"
forever, I don't have a lot of sympathy for anyone choosing different argument
names. (I'm one of them. I'm sure I've written string subclasses that used s
instead of sub.)
I think that the practical benefit in clarity and readability in being able to
write s.find('a', end=42) instead of s.find('a', 0, 42) outweighs the
theoretical harm, but I will accept that there is a downside.
--
Steven
Yes, I only realised after Ethan's reply that my approach puts the
"positional only" parameters in the wrong place relative to normal
parameters (I didn't notice because I'm mainly interested in the
Mapping use case and that doesn't accept any normal parameters - just
positional only and arbitrary keywords).
So, *if* syntactic support for positional-only arguments were added, I
think Guido's syntax would be the way to do it. However, now that I've
realised the "arbitrary keyword arguments" problem can be solved
fairly cleanly by a helper function that binds the positional
arguments, I'm more inclined to just leave it alone and tell people to
just accept *args and process it that way.
OTOH, having a docs-friendly syntax, and better programmatic
introspection for the cases where it does come up would be nice,
too...
> The restrictions on args-without-defaults being unable to follow
> args-with-defaults may need to be revisited so we can combine optional
> positional arguments with required keyword arguments, if we want to
> support that.
Already done:
>>> def f(a, b=1, *, c): return a, b, c
...
>>> f(2, c=3)
(2, 1, 3)
> Nevertheless all this is pretty esoteric and I wouldn't cry if it
> wasn't added. There exist solutions for the Mapping-API problem, and a
> @positional decorator would cover most other cases.
Yep. While I do think it's a slight language wart that we can't
cleanly express all the function and method signatures that are used
by our own builtins and ABC definitions, it's a *very* minor concern
overall.
Cheers,
Nick.
--
Nick Coghlan | ncog...@gmail.com | Brisbane, Australia
> range([start,] stop[, step])
> slice([start,] stop[, step])
> itertools.islice(iterable, [start,] stop [, step])
> random.randrange([start,] stop[, step])
> syslog.syslog([priority,] message)
> curses.newwin([nlines, ncols,] begin_y, begin_x)
> curses.window.addch([y, x,] ch[, attr])
> curses.window.addnstr([y, x,] str, n[, attr])
> curses.window.addstr([y, x,] str[, attr])
> curses.window.chgat([y, x, ] [num,] attr)
> curses.window.derwin([nlines, ncols,] begin_y, begin_x)
> curses.window.hline([y, x,] ch, n)
> curses.window.insch([y, x,] ch[, attr])
> curses.window.insnstr([y, x,] str, n [, attr])
> curses.window.subpad([nlines, ncols,] begin_y, begin_x)
> curses.window.subwin([nlines, ncols,] begin_y, begin_x)
> curses.window.vline([y, x,] ch, n)
I think this use of brackets is really elegant. Not sure if it would work as syntax or not, but it's great at conveying intent.
> So, *if* syntactic support for positional-only arguments were added, I
> think Guido's syntax would be the way to do it. However, now that I've
> realised the "arbitrary keyword arguments" problem can be solved
> fairly cleanly by a helper function that binds the positional
> arguments, I'm more inclined to just leave it alone and tell people to
> just accept *args and process it that way.
Yeah, I think I agree with that for now. I feel signatures are already
pretty complex. If we found a solution that worked while simplifying or
merging some of that complexity in a nice way, I'd be +1.
Your suggested syntax was leaning in that direction I think. I liked
that there was a possibly a clearer separation between positional
arguments and keywords arguments.
If you look at the C code that parses signatures, it's not simple or
straight forward. It's not easy to write a python function that maps
(*args, **kwds) to a signature in the same way. I tried it to test out
some ideas a while back.
What makes it difficult is some of the "*args" values can be keyword
arguments assigned by position. Or some values in "**kwds" values may
be positional arguments assigned by name. I think the order not being
preserved in kwds was also a factor.
While in the simple cases, it's fairly easy to mentally parse a
signature, the mental slope gets steeper as you start to combine the
different concepts into one signature. Maybe it's easy for those who do
it every day, but not as easy for those doing it less often, or for
those who are just beginning to learn python.
> OTOH, having a docs-friendly syntax, and better programmatic
> introspection for the cases where it does come up would be nice,
> too...
When I was looking at your syntax I was thinking of it like this...
def foo(*(a, b=2)->args, **(c=3)->kwds):
...
return args, kwds
Which would map the positional only arguments to args, and the rest to
kwds and include the default values as well. But that's a different
thing.
That wouldn't be friendly to duck typing because functions in the front
of the chain should be not care what the signature of the function at
the end of the chain will be. It would be limited to functions (ducks)
who's signatures are compatible with each other.
The (*args, **kwds) signature only corresponds to positional and
keywords arguments in the simple cases where no positional (only)
argument has a default value, and no keyword arguments are assigned by
position.
As far as better docs-friendly syntax, and introspection are concerned,
I think we will need a signature object that can be introspected. It
also might be helpful in evaluating ideas like these.
Cheers,
Ron
There is no clash. Both the application means the same thing -- you
advise a client not to use this name as keyword argument. Of course, he
may, but need not, if not aware.
'spam'.find('a', _end=1) looks terrible and no one will inadvertently
use it.
Or use a double underscore for the reinforcement to prevent the use of
this name. def find(self, sub, __start=0, __end=None)
> Please do not give syntactic meaning to [parameter], unless it matches
> the existing convention for optional parameters.
Why should it have to do that? We already have a syntax for
optional parameters, and there is no reason for a reader to
think that a new piece of syntax is simply duplicating existing
functionality.
> Besides, positional-only arguments are not only of interest to the
> implementation, they are part of the API.
The fact that a parameter exists in that slot is part of the
API, but the *name* of it is not. This is reflected in the fact
that the comma is outside the brackets and the name is inside.
--
Greg
> Now that I understand that / will only appear in at most one place, like
> * (and not following each and every positional-only arg) this is the
> nicest syntax I've seen yet.
It's reasonably nice, but I'm not sure about giving the '/' its
own slot with commas either side. This works for * and ** because
they (optionally now in the case of *) take a name after them,
but the '/' never will.
So how about treating '/' as a separator instead:
def foo(pos1, pos2 / arg3, arg4, *args, **kwds):
--
Greg
Sorry, I just realised what you meant by that -- you weren't
talking about Python syntax, but rather *metasyntax* used in
documentation. You have a point there.
Looks a lot like division to me. Plus you then have the signature
different from the call (as it *would* be division if you tried to use
it as a separator when calling it).
Unless we have a good reason to treat it differently from a lone '*', I
think we should be consistent and treat it the same. (I obviously don't
think the lack of a name is a good enough reason to be inconsistent. ;)
~Ethan~
I think we need to look at this from the function user's perspective.
For example let's take this hypothetical declaration:
def func(a, b, /, x, y, *, name, color):
This function may be called like this:
func(v1, v2)
func(v1, v2, v3, v4)
func(v1, v2, y=v4, x=v3)
func(v1, v2, x=v3, y=v4)
func(v1, v2, v3, v4, name='westley', color='0xffffff')
func(v1, v2, name='westley', color='0xffffff', x=v3, y=v4)
func(v1, name='westley', color='0xffffff', x=v3, y=v4, v2) # ERROR!
To me, this just feels a little too ... mutable. In C we have one way
to call functions that is equal to it's function declaration. I'd be +1
for functions that have ONLY non-keyword arguments which would be
declared via decorator:
@positional # This name is a bit ambiguous I guess....
def func(a, b)
I see your later comment about metasyntax, but to clarify in case there is
still any lingering doubt what I mean:
When reading function signatures in *documentation*, we often see
func([parameter]) used to indicate that parameter is an optional argument. If
your proposal is enacted, when reading function signatures in *code*, we will
see func([parameter]) used to indicate that you can't use parameter as a
keyword argument.
The two uses clash, which means that every time we see a [parameter] in a
function signature, there will be a moment of disorientation where we have to
decide whether it should be interpreted using the convention for code or the
convention for documentation.
Certainly there are ways of distinguishing the two from context, particularly
if the default value is shown in the signature, or from subtleties of whether
the comma is inside the brackets or not, or perhaps from the order ([] early
in the signature probably means positional, [] at the end probably means
optional).
My point is not that it is impossible to distinguish optional from positional
arguments, but that the similarity of syntax makes it difficult to distinguish
the two *at a glance* and comprehensibility will be reduced.
And heaven help us if we have a function like range with positional-only
optional parameters:
range([[start]=0,] [end] [, [step]=1]) --> iterable
For the avoidance of doubt, I am *not* suggesting that we introduce
[parameter] as syntax for optional arguments. I don't want to see [] used
syntactically inside def func(...) at all, except for the VERY rare use of
lists as mutable default arguments.
--
Steven