[Python-ideas] keyword arguments everywhere (stdlib) - issue8706

38 views
Skip to first unread message

Gregory P. Smith

unread,
Mar 2, 2012, 2:52:05 AM3/2/12
to Python-Ideas, Michael Foord
Something I'd like to bring up at the language summit next week if we have time (suggestion: limit it to 20 minutes), lets start discussion now:

1) Should _all_ Python base types support keyword arguments on all of their methods.
2) Should _all_ stdlib extension modules support keyword arguments on all of their functions and methods?

Assumptions: any newly supported keyword arguments would have meaningful names (and the documentation updated to reflect their name).

How should "_all_" be defined in each of the above.

One example against _all_ is trivial single-argument functions such as chr().
One example for is the str.find method's start parameter being more obvious if named.

An example of a rule of thumb that could be proposed:
  * All optional arguments should also be accepted as keywords.
  * Any public function or method that require multiple arguments should accept them as keywords.

Why propose this?  For consistency and to promote readable code.  A reasonably often heard complaint from people new to Python that I have heard is how some things work as keyword arguments and some don't.  Pure Python code can't (easily without gross hacks) be written to refuse to accept keyword arguments, the standard types and library should not be different.

Documentation and docstrings have at times been inconsistent in the past with respect to what they call arguments and would be cleaned up as part of this to match the actual keyword argument accepted (that is worth doing on its own even without changing what the code accepts).

A decision with discussion notes on this may be PEP worthy.

for reference - this is http://bugs.python.org/issue8706

-gps

Steven D'Aprano

unread,
Mar 2, 2012, 3:43:05 AM3/2/12
to Python-Ideas
Gregory P. Smith wrote:
> Something I'd like to bring up at the language summit next week if we have
> time (suggestion: limit it to 20 minutes), lets start discussion now:
>
> 1) Should _*all_* Python base types support keyword arguments on all of
> their methods.
> 2) Should _*all_* stdlib extension modules support keyword arguments on all

> of their functions and methods?


+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

Nick Coghlan

unread,
Mar 2, 2012, 7:13:13 AM3/2/12
to Steven D'Aprano, Python-Ideas
On Fri, Mar 2, 2012 at 6:43 PM, Steven D'Aprano <st...@pearwood.info> wrote:
> +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.

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

Guido van Rossum

unread,
Mar 2, 2012, 2:28:01 PM3/2/12
to Nick Coghlan, Python-Ideas
On Fri, Mar 2, 2012 at 4:13 AM, Nick Coghlan <ncog...@gmail.com> wrote:
> On Fri, Mar 2, 2012 at 6:43 PM, Steven D'Aprano <st...@pearwood.info> wrote:
>> +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.
>
> 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.

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)

Arnaud Delobelle

unread,
Mar 2, 2012, 2:42:58 PM3/2/12
to Guido van Rossum, Python-Ideas
On 2 March 2012 19:28, Guido van Rossum <gu...@python.org> wrote:
> On Fri, Mar 2, 2012 at 4:13 AM, Nick Coghlan <ncog...@gmail.com> wrote:
>> On Fri, Mar 2, 2012 at 6:43 PM, Steven D'Aprano <st...@pearwood.info> wrote:
>>> +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.
>>
>> 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.
>
> 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).

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

Guido van Rossum

unread,
Mar 2, 2012, 3:00:14 PM3/2/12
to Arnaud Delobelle, Python-Ideas

I've written such decorators too, but they've got quite a bit of overhead...

--Guido van Rossum (sent from Android phone)

Ethan Furman

unread,
Mar 2, 2012, 3:32:21 PM3/2/12
to Guido van Rossum, Python-Ideas
Guido van Rossum wrote:
> 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).

So something like:

def ord(char, ?):

def split(self, char, ?, count)

def canary(breed, ?, color, wingspan, *, name)

~Ethan~

Arnaud Delobelle

unread,
Mar 2, 2012, 4:09:59 PM3/2/12
to Guido van Rossum, Python-Ideas

> 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.

Guido van Rossum

unread,
Mar 2, 2012, 4:48:15 PM3/2/12
to Arnaud Delobelle, Python-Ideas
On Fri, Mar 2, 2012 at 1:09 PM, Arnaud Delobelle <arn...@gmail.com> wrote:
> On 2 March 2012 20:00, Guido van Rossum <gu...@python.org> wrote:
>> On Mar 2, 2012 11:43 AM, "Arnaud Delobelle" <arn...@gmail.com> wrote:
>>> On 2 March 2012 19:28, Guido van Rossum <gu...@python.org> wrote:
>
>>> > 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).
>>>
>>> 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.
>>
>> 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)

Gregory P. Smith

unread,
Mar 2, 2012, 5:01:36 PM3/2/12
to Guido van Rossum, Python-Ideas
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...

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.

speaking of range... I think start and stop are plenty obvious, but I'd like to allow step to be specified as a keyword.

-gps
 

Guido van Rossum

unread,
Mar 2, 2012, 5:18:58 PM3/2/12
to Gregory P. Smith, Python-Ideas
On Fri, Mar 2, 2012 at 2:01 PM, Gregory P. Smith <gr...@krypto.org> wrote:
>
> 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...

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)

Terry Reedy

unread,
Mar 2, 2012, 5:23:59 PM3/2/12
to python...@python.org
On 3/2/2012 3:32 PM, Ethan Furman wrote:
> Guido van Rossum wrote:
>> 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 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

Gregory P. Smith

unread,
Mar 2, 2012, 5:36:42 PM3/2/12
to Guido van Rossum, Python-Ideas
On Fri, Mar 2, 2012 at 2:18 PM, Guido van Rossum <gu...@python.org> wrote:
On Fri, Mar 2, 2012 at 2:01 PM, Gregory P. Smith <gr...@krypto.org> wrote:
>
> 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...

I just want to remain realistic and acknowledge that positional
arguments have their place.

+1
 
> 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.

Technically more than one, if you consider slice() separate from range()... but they are related so I'm willing to consider them "one" ;)

anyways, agreed.  keeping it simple makes sense.

Though the syntax proposals so far aren't looking great to me.  I need to stare at them longer.

-gps

Ethan Furman

unread,
Mar 2, 2012, 5:49:08 PM3/2/12
to python...@python.org
Terry Reedy wrote:
> On 3/2/2012 3:32 PM, Ethan Furman wrote:
>> Guido van Rossum wrote:
>>> 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 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.

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~

Guido van Rossum

unread,
Mar 2, 2012, 5:46:43 PM3/2/12
to Ethan Furman, python...@python.org
On Fri, Mar 2, 2012 at 2:49 PM, Ethan Furman <et...@stoneleaf.us> wrote:
> Terry Reedy wrote:
>>
>> On 3/2/2012 3:32 PM, Ethan Furman wrote:
>>>
>>> Guido van Rossum wrote:
>>>>
>>>> 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 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.
>
>
> 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.

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)

Yury Selivanov

unread,
Mar 2, 2012, 5:55:50 PM3/2/12
to Guido van Rossum, python...@python.org
On 2012-03-02, at 5:46 PM, Guido van Rossum wrote:
> Alternative proposal: how about using '/' ? It's kind of the opposite
> of '*' which means "keyword argument", and '/' is not a new character.

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...

Ethan Furman

unread,
Mar 2, 2012, 6:12:37 PM3/2/12
to Yury Selivanov, python...@python.org
Yury Selivanov wrote:
> On 2012-03-02, at 5:46 PM, Guido van Rossum wrote:
>> Alternative proposal: how about using '/' ? It's kind of the opposite
>> of '*' which means "keyword argument", and '/' is not a new character.
>
> How about ';'? Is it possible to re-use it in this context?
>
> def (a; b, *, c)
> def (; b)

Hmm -- not sure that is obvious enough. Also, your second example
doesn't need the semi-colon at all.

~Ethan~

Ethan Furman

unread,
Mar 2, 2012, 6:09:19 PM3/2/12
to Guido van Rossum, python...@python.org, Terry Reedy
Guido van Rossum wrote:
> On Fri, Mar 2, 2012 at 2:49 PM, Ethan Furman <et...@stoneleaf.us> wrote:
>> Terry Reedy wrote:
>>> On 3/2/2012 3:32 PM, Ethan Furman wrote:
>>>
>>>> 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.
>>
>> 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'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.
>

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~

Nick Coghlan

unread,
Mar 2, 2012, 6:42:01 PM3/2/12
to Guido van Rossum, Python-Ideas
On Sat, Mar 3, 2012 at 5:28 AM, Guido van Rossum <gu...@python.org> wrote:
>> 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.
>
> 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.

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

Terry Reedy

unread,
Mar 2, 2012, 6:45:29 PM3/2/12
to python...@python.org
On 3/2/2012 5:46 PM, Guido van Rossum wrote:

> 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

Chris Rebert

unread,
Mar 2, 2012, 7:01:08 PM3/2/12
to Python-Ideas
On Fri, Mar 2, 2012 at 3:42 PM, Nick Coghlan <ncog...@gmail.com> wrote:
> On Sat, Mar 3, 2012 at 5:28 AM, Guido van Rossum <gu...@python.org> wrote:
<snip>

>> 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.

Kinda incongruous with PEP 3113 though.

- Chris

Ethan Furman

unread,
Mar 2, 2012, 6:56:48 PM3/2/12
to Nick Coghlan, Python-Ideas
Nick Coghlan wrote:
> On Sat, Mar 3, 2012 at 5:28 AM, Guido van Rossum <gu...@python.org> wrote:
>> 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

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~

Mike Graham

unread,
Mar 2, 2012, 7:56:35 PM3/2/12
to Ethan Furman, Python-Ideas
On Fri, Mar 2, 2012 at 6:56 PM, Ethan Furman <et...@stoneleaf.us> wrote:
> 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)

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

Raymond Hettinger

unread,
Mar 2, 2012, 9:22:32 PM3/2/12
to Gregory P. Smith, Python-Ideas

On Mar 2, 2012, at 2:01 PM, Gregory P. Smith wrote:

speaking of range... I think start and stop are plenty obvious, but I'd like to allow step to be specified as a keyword.

range() has been around 20+ years and this has never been requested.
In my teaching of Python, it is never arisen as an issue.
AFAICT, there isn't any code that would be better if step were written as a keyword.

The only expressed motivation for the change is "I'd like" it.
There should be a higher bar for changing builtins.

Many of the proposals in this thread are gratuitous and will create
unnecessary work for other people who have to change anything
that purports to have a range-like interface, people who have to
change the other Python implementations, folks who who have 
to remember which version of Python supports it and which other 
slice-like functions would also take the argument etc.

ISTM that having a ton of tiny nit changes to the language
doesn't make it better.  Instead, effort should be directed
as substantive changes (better https support, completing xmlrpc, etc).
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).

Raymond




Steven D'Aprano

unread,
Mar 2, 2012, 9:57:52 PM3/2/12
to python...@python.org


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

Steven D'Aprano

unread,
Mar 2, 2012, 10:07:02 PM3/2/12
to Python-Ideas
Guido van Rossum wrote:

>> 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

Steven D'Aprano

unread,
Mar 2, 2012, 10:20:09 PM3/2/12
to Python-Ideas
Raymond Hettinger wrote:
> On Mar 2, 2012, at 2:01 PM, Gregory P. Smith wrote:
>
>> speaking of range... I think start and stop are plenty obvious, but I'd like to allow step to be specified as a keyword.
>
> range() has been around 20+ years and this has never been requested.

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

Guido van Rossum

unread,
Mar 2, 2012, 10:31:06 PM3/2/12
to Steven D'Aprano, python...@python.org
On Fri, Mar 2, 2012 at 6:57 PM, Steven D'Aprano <st...@pearwood.info> wrote:
> 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, /).

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)

Chris Rebert

unread,
Mar 2, 2012, 10:31:56 PM3/2/12
to Steven D'Aprano, python...@python.org

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

Guido van Rossum

unread,
Mar 2, 2012, 10:36:39 PM3/2/12
to Steven D'Aprano, Python-Ideas
On Fri, Mar 2, 2012 at 7:20 PM, Steven D'Aprano <st...@pearwood.info> wrote:
> Raymond Hettinger wrote:
>>
>> On Mar 2, 2012, at 2:01 PM, Gregory P. Smith wrote:
>>
>>> speaking of range... I think start and stop are plenty obvious, but I'd
>>> like to allow step to be specified as a keyword.
>>
>>
>> range() has been around 20+ years and this has never been requested.
>
>
> 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)?

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)

Ethan Furman

unread,
Mar 3, 2012, 2:54:58 AM3/3/12
to Guido van Rossum, python...@python.org
Guido van Rossum wrote:

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~

Serhiy Storchaka

unread,
Mar 3, 2012, 4:21:22 AM3/3/12
to python...@python.org
03.03.12 00:18, Guido van Rossum написав(ла):

> On Fri, Mar 2, 2012 at 2:01 PM, Gregory P. Smith<gr...@krypto.org> wrote:
>>
> 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.

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.

Serhiy Storchaka

unread,
Mar 3, 2012, 4:29:09 AM3/3/12
to python...@python.org
03.03.12 05:20, Steven D'Aprano написав(ла):

> 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)?

You can write print(1, 2, 3, end='') but can't write... what?

Serhiy Storchaka

unread,
Mar 3, 2012, 4:53:08 AM3/3/12
to python...@python.org
03.03.12 00:46, Guido van Rossum написав(ла):

> Alternative proposal: how about using '/' ? It's kind of the opposite
> of '*' which means "keyword argument", and '/' is not a new character.

How about using '**' (and left '/' for some purpose in the future)?

Serhiy Storchaka

unread,
Mar 3, 2012, 4:53:59 AM3/3/12
to python...@python.org
03.03.12 00:55, Yury Selivanov написав(ла):

> How about ';'? Is it possible to re-use it in this context?
>
> def (a; b, *, c)
> def (; b)

How about lambdas?

foo = lambda a; b: return a

Arnaud Delobelle

unread,
Mar 3, 2012, 8:51:12 AM3/3/12
to Guido van Rossum, Python-Ideas
On 2 March 2012 21:48, Guido van Rossum <gu...@python.org> wrote:
> On Fri, Mar 2, 2012 at 1:09 PM, Arnaud Delobelle <arn...@gmail.com> wrote:
>> On 2 March 2012 20:00, Guido van Rossum <gu...@python.org> wrote:
>>> On Mar 2, 2012 11:43 AM, "Arnaud Delobelle" <arn...@gmail.com> wrote:

>>>> On 2 March 2012 19:28, Guido van Rossum <gu...@python.org> wrote:
>>
>>>> > 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).
>>>>
>>>> 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.

>>>
>>> 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.

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

Ron Adam

unread,
Mar 3, 2012, 10:20:13 AM3/3/12
to python...@python.org, ron...@gmail.com
On Fri, 2012-03-02 at 18:22 -0800, Raymond Hettinger wrote:
>
> On Mar 2, 2012, at 2:01 PM, Gregory P. Smith wrote:
>
> > speaking of range... I think start and stop are plenty obvious, but
> > I'd like to allow step to be specified as a keyword.
>
>
> range() has been around 20+ years and this has never been requested.
> In my teaching of Python, it is never arisen as an issue.
> AFAICT, there isn't any code that would be better if step were written
> as a keyword.
>
>
> The only expressed motivation for the change is "I'd like" it.
> There should be a higher bar for changing builtins.
>
>
> Many of the proposals in this thread are gratuitous and will create
> unnecessary work for other people who have to change anything
> that purports to have a range-like interface, people who have to
> change the other Python implementations, folks who who have
> to remember which version of Python supports it and which other
> slice-like functions would also take the argument etc.
>
>
> ISTM that having a ton of tiny nit changes to the language
> doesn't make it better. Instead, effort should be directed
> as substantive changes (better https support, completing xmlrpc, etc).

+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

Simon Sapin

unread,
Mar 3, 2012, 10:47:21 AM3/3/12
to python...@python.org
Le 03/03/2012 16:20, Ron Adam a écrit :
> 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 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

Guido van Rossum

unread,
Mar 3, 2012, 12:54:03 PM3/3/12
to ron...@gmail.com, python...@python.org
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.

> 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)

Gregory P. Smith

unread,
Mar 3, 2012, 2:54:54 PM3/3/12
to Arnaud Delobelle, Python-Ideas
On Sat, Mar 3, 2012 at 5:51 AM, Arnaud Delobelle <arn...@gmail.com> wrote:

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.


+1 Agreed.

-gps

Terry Reedy

unread,
Mar 3, 2012, 3:01:39 PM3/3/12
to python...@python.org
On 3/3/2012 8:51 AM, Arnaud Delobelle wrote:

> 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

Terry Reedy

unread,
Mar 3, 2012, 3:11:14 PM3/3/12
to python...@python.org
On 3/3/2012 12:54 PM, Guido van Rossum wrote:

> 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

Guido van Rossum

unread,
Mar 3, 2012, 3:39:32 PM3/3/12
to Terry Reedy, python...@python.org
On Sat, Mar 3, 2012 at 12:11 PM, Terry Reedy <tjr...@udel.edu> wrote:
> On 3/3/2012 12:54 PM, Guido van Rossum wrote:
>
>> 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.

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)

Simon Sapin

unread,
Mar 3, 2012, 4:03:47 PM3/3/12
to python...@python.org
Le 03/03/2012 21:39, Guido van Rossum a écrit :
> Yeah, so it does make sense to standardize on a solution for this. Let
> it be @positional(N).

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

Georg Brandl

unread,
Mar 3, 2012, 4:29:27 PM3/3/12
to python...@python.org
On 03.03.2012 21:11, Terry Reedy wrote:
> On 3/3/2012 12:54 PM, Guido van Rossum wrote:
>
>> 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

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.)

Cameron Simpson

unread,
Mar 3, 2012, 4:40:11 PM3/3/12
to Serhiy Storchaka, python...@python.org
On 03Mar2012 11:53, Serhiy Storchaka <stor...@gmail.com> wrote:
| 03.03.12 00:46, Guido van Rossum написав(ла):
| > Alternative proposal: how about using '/' ? It's kind of the opposite
| > of '*' which means "keyword argument", and '/' is not a new character.
|
| How about using '**' (and left '/' for some purpose in the future)?

-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

Serhiy Storchaka

unread,
Mar 3, 2012, 4:54:39 PM3/3/12
to python...@python.org
03.03.12 23:40, Cameron Simpson написав(ла):

> On 03Mar2012 11:53, Serhiy Storchaka<stor...@gmail.com> wrote:
> | 03.03.12 00:46, Guido van Rossum написав(ла):
> |> Alternative proposal: how about using '/' ? It's kind of the opposite
> |> of '*' which means "keyword argument", and '/' is not a new character.
> |
> | How about using '**' (and left '/' for some purpose in the future)?
>
> -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.

"*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.

Cameron Simpson

unread,
Mar 3, 2012, 5:17:22 PM3/3/12
to Gregory P. Smith, Python-Ideas
On 02Mar2012 14:01, Gregory P. Smith <gr...@krypto.org> wrote:

| 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...

|
| 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.

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...

Give me the luxuries of life and I will willingly do without the necessities.
- Frank Lloyd Wright

Ethan Furman

unread,
Mar 3, 2012, 6:10:39 PM3/3/12
to python...@python.org
Georg Brandl wrote:
> On 03.03.2012 21:11, Terry Reedy wrote:
>> On 3/3/2012 12:54 PM, Guido van Rossum wrote:
>>
>>> 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
>
> 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.)

Also -0 for the same reason.

~Ethan~

Ron Adam

unread,
Mar 3, 2012, 7:15:48 PM3/3/12
to Guido van Rossum, ron...@gmail.com, python...@python.org

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.

_______________________________________________

Guido van Rossum

unread,
Mar 3, 2012, 7:46:29 PM3/3/12
to ron...@gmail.com, python...@python.org
On Sat, Mar 3, 2012 at 4:15 PM, Ron Adam <ron...@gmail.com> wrote:
> 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>

Apparently you skipped part of the thread. <shrug**2>

--
--Guido van Rossum (python.org/~guido)

Antoine Pitrou

unread,
Mar 3, 2012, 7:55:02 PM3/3/12
to python...@python.org
On Sat, 3 Mar 2012 09:54:03 -0800

Guido van Rossum <gu...@python.org> wrote:
>
> > 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.

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.

Guido van Rossum

unread,
Mar 3, 2012, 9:18:52 PM3/3/12
to Antoine Pitrou, python...@python.org
On Sat, Mar 3, 2012 at 4:55 PM, Antoine Pitrou <soli...@pitrou.net> wrote:
> On Sat, 3 Mar 2012 09:54:03 -0800
> Guido van Rossum <gu...@python.org> wrote:
>>
>> > 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.
>
> 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.

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)

Steven D'Aprano

unread,
Mar 3, 2012, 10:42:30 PM3/3/12
to python...@python.org

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

Nick Coghlan

unread,
Mar 3, 2012, 10:46:33 PM3/3/12
to Guido van Rossum, python...@python.org, Terry Reedy
On Sun, Mar 4, 2012 at 6:39 AM, Guido van Rossum <gu...@python.org> wrote:
> Yeah, so it does make sense to standardize on a solution for this. Let
> it be @positional(N). Can you file an issue?

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

Ron Adam

unread,
Mar 3, 2012, 11:16:29 PM3/3/12
to Guido van Rossum, ron...@gmail.com, python...@python.org
On Sat, 2012-03-03 at 16:46 -0800, Guido van Rossum wrote:
> On Sat, Mar 3, 2012 at 4:15 PM, Ron Adam <ron...@gmail.com> wrote:
> > 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>
>
> Apparently you skipped part of the thread. <shrug**2>

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

David Townshend

unread,
Mar 4, 2012, 2:16:32 AM3/4/12
to ron...@gmail.com, python...@python.org
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?

2.  How to avoid the problems with name-binding to an intended positional only argument.  Once again, this can be dealt with using *args.

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)

>>> f(1, 2, 'other', 'args', pos='arguments')
(1, 2)
'other'
('args',)
{'pos': 'arguments'}

I'm +0 on the whole idea, but only if it deals with both issues.

David

Antoine Pitrou

unread,
Mar 4, 2012, 9:00:59 AM3/4/12
to python...@python.org
On Sun, 04 Mar 2012 14:42:30 +1100
Steven D'Aprano <st...@pearwood.info> wrote:
>
> >>> 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! :-)

@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.

Guido van Rossum

unread,
Mar 4, 2012, 11:23:23 AM3/4/12
to Steven D'Aprano, python...@python.org
On Sat, Mar 3, 2012 at 7:42 PM, Steven D'Aprano <st...@pearwood.info> wrote:
> 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!

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

unread,
Mar 4, 2012, 11:24:27 AM3/4/12
to Ethan Furman, python...@python.org


On Sun, Mar 4, 2012 at 5:34 PM, Ethan Furman <et...@stoneleaf.us> wrote:
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.


Agreed, but why not this?

def f(*args, **kwargs):
    assert len(args) == 2

The exception raised could easily be a TypeError too, so it would appear, to a user, the same as defining it in the function signature.  There are of course, other limitations (e.g. introspection), but without a specific use case it is difficult to know how important those limitations are.
 


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.

Why does the user need the names?  If the user cannot use them as keywords, then it doesn't matter what the names are, so anything can be used in the documentation (which is the only place they would appear).  The author doesn't need the names either, just the data they refer to, in this case a tuple.
 

For example:

 str.split(pos(2))

Quick, what should be supplied for the two positional arguments?

Is that a function call or definition?  My suggestion was to use the parenthesis in the definition, not the call.  Since str.split only has optional arguments its not a good example, but if you were to redefine str.replace (in python) it would look like this:

def replace(pos(3), count=None):
    self, old, new = pos
    ...

It would still be called in the same way though:

>>> 'some string'.replace('s', 't', 1)
'tome string'


We want the arguments bound to names *in the function* -- we don't want the arguments bound to names *in the function call*.

That is what I proposed, I just suggested binding all of the positinoal-only arguments to a single name.

Having given it a bit more thought, though, maybe it would be easier to optionally apply the parenthesis to *args:

def replace(self, *args(2), count=None);
   old, new = args

This looks much closer to current situation, and I suppose could be extended to **kwargs, limiting the number of keyword arguments (although I have no idea why this would ever be wanted!)

Ethan Furman

unread,
Mar 4, 2012, 10:34:49 AM3/4/12
to David Townshend, python...@python.org
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*.

~Ethan~

Guido van Rossum

unread,
Mar 4, 2012, 11:37:27 AM3/4/12
to Nick Coghlan, python...@python.org, Terry Reedy
On Sat, Mar 3, 2012 at 7:46 PM, Nick Coghlan <ncog...@gmail.com> wrote:
> On Sun, Mar 4, 2012 at 6:39 AM, Guido van Rossum <gu...@python.org> wrote:
>> Yeah, so it does make sense to standardize on a solution for this. Let
>> it be @positional(N). Can you file an issue?
>
> 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)

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).

--
--Guido van Rossum (python.org/~guido)

Antoine Pitrou

unread,
Mar 4, 2012, 11:57:58 AM3/4/12
to python...@python.org

Then please consider also re-introducing parameter tuple unpacking,
since that was genuinely useful.

Regards

Antoine.

Guido van Rossum

unread,
Mar 4, 2012, 12:15:52 PM3/4/12
to Antoine Pitrou, Nick Coghlan, python...@python.org

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)

Ethan Furman

unread,
Mar 4, 2012, 12:20:07 PM3/4/12
to Guido van Rossum, python...@python.org, Terry Reedy
Guido van Rossum wrote:
> On Sat, Mar 3, 2012 at 7:46 PM, Nick Coghlan <ncog...@gmail.com> wrote:
>> 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.
>>
>> 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).

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?

Ethan Furman

unread,
Mar 4, 2012, 12:05:07 PM3/4/12
to Antoine Pitrou, python...@python.org
Antoine Pitrou wrote:
> Then please consider also re-introducing parameter tuple unpacking,
> since that was genuinely useful.

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~

Guido van Rossum

unread,
Mar 4, 2012, 1:15:50 PM3/4/12
to Ethan Furman, python...@python.org, Terry Reedy
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.

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)

Terry Reedy

unread,
Mar 4, 2012, 3:59:56 PM3/4/12
to python...@python.org
On 3/3/2012 3:39 PM, Guido van Rossum wrote:

> 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

Greg Ewing

unread,
Mar 4, 2012, 4:23:34 PM3/4/12
to python...@python.org
Another bikeshed idea on positional-only parameters:

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

Serhiy Storchaka

unread,
Mar 4, 2012, 4:47:28 PM3/4/12
to python...@python.org
04.03.12 23:23, Greg Ewing написав(ла):

> Another bikeshed idea on positional-only parameters:
>
> 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.

Or _name, as for "private" class and module members.

Serhiy Storchaka

unread,
Mar 4, 2012, 5:03:41 PM3/4/12
to python...@python.org
04.03.12 22:59, Terry Reedy написав(ла):

> 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.

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.

Steven D'Aprano

unread,
Mar 4, 2012, 5:28:15 PM3/4/12
to python...@python.org
Serhiy Storchaka wrote:
> 04.03.12 23:23, Greg Ewing написав(ла):
>> Another bikeshed idea on positional-only parameters:
>>
>> 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.

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

Steven D'Aprano

unread,
Mar 4, 2012, 5:32:10 PM3/4/12
to python...@python.org
Guido van Rossum wrote:
> 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.

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

Michael Foord

unread,
Mar 4, 2012, 5:44:15 PM3/4/12
to Steven D'Aprano, python...@python.org
On 4 March 2012 22:32, Steven D'Aprano <st...@pearwood.info> wrote:
Guido van Rossum wrote:
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.

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.


Agreed. However I've *never* wanted to create a "positional args only" parameter to an api that wasn't covered by *args. I also think that in *general* allowing keyword args is an improvement to APIs.

So I guess I'm -0 on the feature, but the "/" syntax seems the best of the ones suggested so far.

Michael


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

Steven D'Aprano

unread,
Mar 4, 2012, 6:32:38 PM3/4/12
to python...@python.org
Guido van Rossum wrote:
> On Sat, Mar 3, 2012 at 7:42 PM, Steven D'Aprano <st...@pearwood.info> wrote:
>> 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!
>
> No, because str may be subclassed, so this change would break
> backwards compatibility for subclasses that chose a different name
> instead of 'end'.

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

Nick Coghlan

unread,
Mar 4, 2012, 7:31:14 PM3/4/12
to Guido van Rossum, python...@python.org, Terry Reedy
On Mon, Mar 5, 2012 at 4:15 AM, Guido van Rossum <gu...@python.org> wrote:
> 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.

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

Carl M. Johnson

unread,
Mar 5, 2012, 12:34:26 AM3/5/12
to python...@python.org

On Mar 2, 2012, at 11:21 PM, Serhiy Storchaka wrote:

> 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.

Ron Adam

unread,
Mar 5, 2012, 1:55:37 AM3/5/12
to python...@python.org, ron...@gmail.com
On Mon, 2012-03-05 at 10:31 +1000, Nick Coghlan wrote:

> 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

Serhiy Storchaka

unread,
Mar 5, 2012, 3:27:28 AM3/5/12
to python...@python.org
05.03.12 00:28, Steven D'Aprano написав(ла):

> Serhiy Storchaka wrote:
>> 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.

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)

Greg Ewing

unread,
Mar 5, 2012, 4:30:47 PM3/5/12
to python...@python.org
Steven D'Aprano wrote:

> 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

Greg Ewing

unread,
Mar 5, 2012, 4:35:13 PM3/5/12
to python...@python.org
Steven D'Aprano 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.

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

Greg Ewing

unread,
Mar 5, 2012, 4:53:27 PM3/5/12
to python...@python.org
I wrote:
> Steven D'Aprano wrote:
>
>> Please do not give syntactic meaning to [parameter], unless it matches
>> the existing convention for optional parameters.
>
> Why should it have to do that?

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.

Ethan Furman

unread,
Mar 5, 2012, 5:23:16 PM3/5/12
to Greg Ewing, python...@python.org
Greg Ewing wrote:
> Steven D'Aprano 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.
>
> 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):

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~

Westley Martínez

unread,
Mar 5, 2012, 6:01:46 PM3/5/12
to python...@python.org
I'm -1 on this issue after some thought:

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)

Steven D'Aprano

unread,
Mar 5, 2012, 7:00:31 PM3/5/12
to python...@python.org
Greg Ewing wrote:
> Steven D'Aprano wrote:
>
>> 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.

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

Reply all
Reply to author
Forward
0 new messages