Keyword-only arguments?

One thing that has been a source of bugs and frustration in the past is the inability to designate a named keyword argument that cannot be passed as a positional argument (short of **kwargs and then keying into the dict directly). Has there been any previous discussion on the possibility of a means to designate named arguments as explicitly non-positional? Not a solid proposal, but to capture the essential difference of what I'm thinking of, along the lines of... def foo(bar, baz=None, qux: None): where bar is a required positional argument, baz is an optional argument that can have a value passed positionally or by name, and qux is an optional argument that must always be passed by keyword. Such a means would help avoid cases where a misremembered function signature results in a subtle and likely unnoticed bug due to unintended parameter/argument mismatch. (It's possible that this has been discussed before - a cursory search of python-ideas didn't bring up any direct discussion, but I may have missed something. If you have a link to prior discussion, please by all means point me at it!)

https://www.python.org/dev/peps/pep-3102/ On Wed, Jun 17, 2015 at 2:58 PM, Amber Yust <amber.yust@gmail.com> wrote:

On Wed, Jun 17, 2015 at 11:58 AM, Amber Yust <amber.yust@gmail.com> wrote:
This feature was added to Python 3 about 9 years ago, see https://www.python.org/dev/peps/pep-3102/. A quick search for "python keyword only arguments" on Google found it. Guido's time machine strikes again! Chris

On 06/17/2015 12:11 PM, Amber Yust wrote:
Interesting. I don't think I've ever seen it used, even having looked at Python 3 code. For those who have worked with more Python 3 code than I have, do you ever see it used?
We don't typically go back and modify existing code to use new features, so your best bet to see it used is to find new features in Python 3. Or, do a grep on the source code: base64.py:def a85encode(b, *, foldspaces=False, wrapcol=0, pad=False, adobe=False): base64.py:def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v'): codecs.py: *, _is_text_encoding=None): configparser.py: allow_no_value=False, *, delimiters=('=', ':'), configparser.py: def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET): configparser.py: def _get_conv(self, section, option, conv, *, raw=False, vars=None, configparser.py: def getint(self, section, option, *, raw=False, vars=None, configparser.py: def getfloat(self, section, option, *, raw=False, vars=None, configparser.py: def getboolean(self, section, option, *, raw=False, vars=None, configparser.py: def _validate_value_types(self, *, section="", option="", value=""): configparser.py: def get(self, option, fallback=None, *, raw=False, vars=None, datetime.py:# standard time. Since that's what the local clock *does*, we want to map both difflib.py: context=False, numlines=5, *, charset='utf-8'): dis.py:def dis(x=None, *, file=None): dis.py:def distb(tb=None, *, file=None): dis.py:def show_code(co, *, file=None): dis.py:def get_instructions(x, *, first_line=None): dis.py:def disassemble(co, lasti=-1, *, file=None): dis.py: *, file=None, line_offset=0): dis.py:def _disassemble_str(source, *, file=None): dis.py: def __init__(self, x, *, first_line=None, current_offset=None): enum.py: def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, start=1): enum.py: def _create_(cls, class_name, names=None, *, module=None, qualname=None, type=None, start=1): functools.py: # an ABC *base*, insert said ABC to its MRO. glob.py:def glob(pathname, *, recursive=False): glob.py:def iglob(pathname, *, recursive=False): inspect.py:attributes (co_*, im_*, tb_*, etc.) in a friendlier fashion. inspect.py:def unwrap(func, *, stop=None): inspect.py: # signature: "(*, a='spam', b, c)". Because attempting inspect.py:def _signature_from_callable(obj, *, inspect.py: def __init__(self, name, kind, *, default=_empty, annotation=_empty): inspect.py: def replace(self, *, name=_void, kind=_void, inspect.py: def __init__(self, parameters=None, *, return_annotation=_empty, inspect.py: def from_callable(cls, obj, *, follow_wrapped=True): inspect.py: def replace(self, *, parameters=_void, return_annotation=_void): inspect.py: def _bind(self, args, kwargs, *, partial=False): inspect.py: # separator to the parameters list ("foo(arg1, *, arg2)" case) inspect.py:def signature(obj, *, follow_wrapped=True): lzma.py: def __init__(self, filename=None, mode="r", *, lzma.py:def open(filename, mode="rb", *, lzma.py: optional arguments *format*, *check*, *preset* and *filters*. lzma.py: optional arguments *format*, *check* and *filters*. nntplib.py: def newgroups(self, date, *, file=None): nntplib.py: def newnews(self, group, date, *, file=None): nntplib.py: def list(self, group_pattern=None, *, file=None): nntplib.py: def help(self, *, file=None): nntplib.py: def head(self, message_spec=None, *, file=None): nntplib.py: def body(self, message_spec=None, *, file=None): nntplib.py: def article(self, message_spec=None, *, file=None): nntplib.py: def xhdr(self, hdr, str, *, file=None): nntplib.py: def xover(self, start, end, *, file=None): nntplib.py: def over(self, message_spec, *, file=None): nntplib.py: def xgtitle(self, group, *, file=None): ntpath.py:# See also module 'glob' for expansion of *, ? and [...] in pathnames. numbers.py: *, /, abs(), .conjugate, ==, and !=. os.py: def fwalk(top=".", topdown=True, onerror=None, *, follow_symlinks=False, dir_fd=None): pickle.py: def __init__(self, file, protocol=None, *, fix_imports=True): pickle.py: def __init__(self, file, *, fix_imports=True, pickle.py: Optional keyword arguments are *fix_imports*, *encoding* and pickle.py: *errors*, which are used to control compatiblity support for pickle.py:def _dump(obj, file, protocol=None, *, fix_imports=True): pickle.py:def _dumps(obj, protocol=None, *, fix_imports=True): pickle.py:def _load(file, *, fix_imports=True, encoding="ASCII", errors="strict"): pickle.py:def _loads(s, *, fix_imports=True, encoding="ASCII", errors="strict"): plistlib.py:def load(fp, *, fmt=None, use_builtin_types=True, dict_type=dict): plistlib.py:def loads(value, *, fmt=None, use_builtin_types=True, dict_type=dict): plistlib.py:def dump(value, fp, *, fmt=FMT_XML, sort_keys=True, skipkeys=False): plistlib.py:def dumps(value, *, fmt=FMT_XML, skipkeys=False, sort_keys=True): posixpath.py:# See also module 'glob' for expansion of *, ? and [...] in pathnames. pprint.py:def pprint(object, stream=None, indent=1, width=80, depth=None, *, pprint.py:def pformat(object, indent=1, width=80, depth=None, *, compact=False): pprint.py: def __init__(self, indent=1, width=80, depth=None, stream=None, *, pydoc.py:def browse(port=0, *, open_browser=True): _pyio.py: *opener* with (*file*, *flags*). *opener* must return an open file _pyio.py: """Read up to len(b) bytes into *b*, using at most one system call _pyio.py: object is then obtained by calling opener with (*name*, *flags*). shutil.py:def copyfile(src, dst, *, follow_symlinks=True): shutil.py:def copymode(src, dst, *, follow_symlinks=True): shutil.py: def _copyxattr(src, dst, *, follow_symlinks=True): shutil.py:def copystat(src, dst, *, follow_symlinks=True): shutil.py:def copy(src, dst, *, follow_symlinks=True): shutil.py:def copy2(src, dst, *, follow_symlinks=True): socket.py: def makefile(self, mode="r", buffering=None, *, ssl.py:def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None, ssl.py:def _create_unverified_context(protocol=PROTOCOL_SSLv23, *, cert_reqs=None, tarfile.py: def list(self, verbose=True, *, members=None): tarfile.py: def add(self, name, arcname=None, recursive=True, exclude=None, *, filter=None): tarfile.py: def extractall(self, path=".", members=None, *, numeric_owner=False): tarfile.py: def extract(self, member, path="", set_attrs=True, *, numeric_owner=False): textwrap.py: *, textwrap.py: the *width*, it is returned as is. Otherwise, as many words threading.py: args=(), kwargs=None, *, daemon=None): timeit.py:def main(args=None, *, _wrap_timer=None): traceback.py: def __init__(self, filename, lineno, name, *, lookup_line=True, traceback.py: def extract(klass, frame_gen, *, limit=None, lookup_lines=True, traceback.py: def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, traceback.py: def format(self, *, chain=True): traceback.py: If chain is not *True*, *__cause__* and *__context__* will not be formatted. typing.py: def __new__(cls, name, bases, namespace, *, _root=False): warnings.py: def __init__(self, *, record=False, module=None): -- ~Ethan~

On Jun 17, 2015 12:11 PM, "Amber Yust" <amber.yust@gmail.com> wrote:
Interesting. I don't think I've ever seen it used, even having looked at
Python 3 code. For those who have worked with more Python 3 code than I have, do you ever see it used? Unfortunately, no, because at this point almost all py3 APIs I see are still aiming for py2/py3 compatibility, and there's really no good way to accomplish kw only args in py2. It can be done, but it's very cumbersome; not like range or print or whatever where you can just import something from six or add some parentheses. In retrospect I wish this had been backported to 2.7, because they're super useful for making better APIs, but that ship has sailed. -n

In retrospect I wish this had been backported to 2.7, because they're super useful for making better APIs, but that ship has sailed
Why would this not be able to be ported now? It does not clash with any existing python 2 syntax so all current python 2 is still valid and has no behaviour change. On Wed, Jun 17, 2015 at 3:32 PM, Nathaniel Smith <njs@pobox.com> wrote:

On Wed, Jun 17, 2015 at 3:36 PM, Joseph Jevnik <joejev@gmail.com> wrote:
Python 2.7 is not getting new features (ssl changes notwithstanding), and there will never be a Python 2.8. There's certainly no desire to add new syntax so there would be code that will run in Python 2.7.11 only, and not in earlier 2.7 releases.

On 18 Jun 2015 6:27 am, "Geoffrey Spear" <geoffspear@gmail.com> wrote:
On Wed, Jun 17, 2015 at 3:36 PM, Joseph Jevnik <joejev@gmail.com> wrote:
In retrospect I wish this had been backported to 2.7, because they're
super useful for making better APIs, but that ship has sailed there will never be a Python 2.8. There's certainly no desire to add new syntax so there would be code that will run in Python 2.7.11 only, and not in earlier 2.7 releases. Exactly - it's only in truly exceptional cases like the PEP 466 & 476 network security changes that we'll add features to Python 2.7. Keyword-only arguments are certainly a nice enhancement, but their absence isn't actively harmful the way the aging network security capabilities were. Regards, Nick.

On Wed, 17 Jun 2015 at 21:33 Nathaniel Smith <njs@pobox.com> wrote:
As you correctly point out, it can't be done without friction. I've attempted backporting kw-only parameters through decorators: from sigtools import modifiers @modifiers.kwoargs('kwop') def func(abc, kwop): ... @modifiers.autokwoargs def func(abc, kwop=False): ... http://sigtools.readthedocs.org/en/latest/#sigtools.modifiers.kwoargs

OK, i think it’s time to finally switch to python 3 instead of writing more horrible crutches: # coding: utf-8from __future__ import absolute_import, division, print_function, unicode_literalsfrom builtins import * import trolliusfrom trollius import From, Return from other import stuff @trollius.coroutine@modifiers.kwoargs('b')def awesome_stuff(a, b=5): res = (yield From(stuff())) raise Return(res) vs. import asyncio from other import stuff @asyncio.coroutinedef awesome_stuff(a, *, b=5): res = (yield from stuff()) return res or soon: from other import stuff async def awesome_stuff(a, *, b=5): res = await stuff() return res Yann Kaiser kaiser.yann@gmail.com <http://mailto:kaiser.yann@gmail.com> schrieb am Sa., 20. Juni 2015 um 19:52 Uhr: On Wed, 17 Jun 2015 at 21:33 Nathaniel Smith <njs@pobox.com> wrote:

Definitely agree with wanting to move on and focus on Python 3. I design my stuff with Python 3 first in mind, but reality tells me I need to keep supporting Python 2 users, even if that means uglifying front-page examples with things such as sigtools.modifiers.kwoargs. The existence of this thread and of some of the emails in the "signature documentation" thread only serves as proof that I'd confuse too many by keeping "kwoargs" & co a side-note. Developing on Python 3.4 is great, things like chained tracebacks are fantastic. If I can do it on Python 3, I will. But what if I want to deploy to GAE? Stuck with 2.7. So are many users. It's ugly, it sucks, but so far it is out of my hands and necessary. On Sat, 20 Jun 2015 at 20:13 Philipp A. <flying-sheep@web.de> wrote:

sure! and having those workarounds if you really need them is great. it’s just really frustrating that so many of you are stuck with 2.7 or even less without more reason than “sysadmin won’t install a SCL”. Yann Kaiser <kaiser.yann@gmail.com> schrieb am Sa., 20. Juni 2015 um 20:28 Uhr:

My approach to this particular case has always been, if I need to port keyword-only args to older Python versions, I'll just remove the '*' and make it a documented convention that those arguments must be passed by keyword only, without enforcement. The code obfuscation is just not worth it -- it's just rarely super-important to strictly enforce this convention. -- --Guido van Rossum (python.org/~guido)

On Jun 17, 2015 8:58 PM, "Amber Yust" <amber.yust@gmail.com> wrote:
One thing that has been a source of bugs and frustration in the past is
the inability to designate a named keyword argument that cannot be passed as a positional argument (short of **kwargs and then keying into the dict directly). Has there been any previous discussion on the possibility of a means to designate named arguments as explicitly non-positional?
Not a solid proposal, but to capture the essential difference of what I'm
thinking of, along the lines of...
def foo(bar, baz=None, qux: None):
where bar is a required positional argument, baz is an optional argument
that can have a value passed positionally or by name, and qux is an optional argument that must always be passed by keyword.
Such a means would help avoid cases where a misremembered function
signature results in a subtle and likely unnoticed bug due to unintended parameter/argument mismatch.
(It's possible that this has been discussed before - a cursory search of
python-ideas didn't bring up any direct discussion, but I may have missed something. If you have a link to prior discussion, please by all means point me at it!)
Already present in python 3: https://www.python.org/dev/peps/pep-3102/

Hi Amber, On 06/17/2015 12:58 PM, Amber Yust wrote:
I can do better than prior discussion - this already exists in Python 3: Python 3.4.2 (default, Dec 12 2014, 17:46:08) [GCC 4.8.2] on linux Type "help", "copyright", "credits" or "license" for more information.

https://www.python.org/dev/peps/pep-3102/ On Wed, Jun 17, 2015 at 2:58 PM, Amber Yust <amber.yust@gmail.com> wrote:

On Wed, Jun 17, 2015 at 11:58 AM, Amber Yust <amber.yust@gmail.com> wrote:
This feature was added to Python 3 about 9 years ago, see https://www.python.org/dev/peps/pep-3102/. A quick search for "python keyword only arguments" on Google found it. Guido's time machine strikes again! Chris

On 06/17/2015 12:11 PM, Amber Yust wrote:
Interesting. I don't think I've ever seen it used, even having looked at Python 3 code. For those who have worked with more Python 3 code than I have, do you ever see it used?
We don't typically go back and modify existing code to use new features, so your best bet to see it used is to find new features in Python 3. Or, do a grep on the source code: base64.py:def a85encode(b, *, foldspaces=False, wrapcol=0, pad=False, adobe=False): base64.py:def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v'): codecs.py: *, _is_text_encoding=None): configparser.py: allow_no_value=False, *, delimiters=('=', ':'), configparser.py: def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET): configparser.py: def _get_conv(self, section, option, conv, *, raw=False, vars=None, configparser.py: def getint(self, section, option, *, raw=False, vars=None, configparser.py: def getfloat(self, section, option, *, raw=False, vars=None, configparser.py: def getboolean(self, section, option, *, raw=False, vars=None, configparser.py: def _validate_value_types(self, *, section="", option="", value=""): configparser.py: def get(self, option, fallback=None, *, raw=False, vars=None, datetime.py:# standard time. Since that's what the local clock *does*, we want to map both difflib.py: context=False, numlines=5, *, charset='utf-8'): dis.py:def dis(x=None, *, file=None): dis.py:def distb(tb=None, *, file=None): dis.py:def show_code(co, *, file=None): dis.py:def get_instructions(x, *, first_line=None): dis.py:def disassemble(co, lasti=-1, *, file=None): dis.py: *, file=None, line_offset=0): dis.py:def _disassemble_str(source, *, file=None): dis.py: def __init__(self, x, *, first_line=None, current_offset=None): enum.py: def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, start=1): enum.py: def _create_(cls, class_name, names=None, *, module=None, qualname=None, type=None, start=1): functools.py: # an ABC *base*, insert said ABC to its MRO. glob.py:def glob(pathname, *, recursive=False): glob.py:def iglob(pathname, *, recursive=False): inspect.py:attributes (co_*, im_*, tb_*, etc.) in a friendlier fashion. inspect.py:def unwrap(func, *, stop=None): inspect.py: # signature: "(*, a='spam', b, c)". Because attempting inspect.py:def _signature_from_callable(obj, *, inspect.py: def __init__(self, name, kind, *, default=_empty, annotation=_empty): inspect.py: def replace(self, *, name=_void, kind=_void, inspect.py: def __init__(self, parameters=None, *, return_annotation=_empty, inspect.py: def from_callable(cls, obj, *, follow_wrapped=True): inspect.py: def replace(self, *, parameters=_void, return_annotation=_void): inspect.py: def _bind(self, args, kwargs, *, partial=False): inspect.py: # separator to the parameters list ("foo(arg1, *, arg2)" case) inspect.py:def signature(obj, *, follow_wrapped=True): lzma.py: def __init__(self, filename=None, mode="r", *, lzma.py:def open(filename, mode="rb", *, lzma.py: optional arguments *format*, *check*, *preset* and *filters*. lzma.py: optional arguments *format*, *check* and *filters*. nntplib.py: def newgroups(self, date, *, file=None): nntplib.py: def newnews(self, group, date, *, file=None): nntplib.py: def list(self, group_pattern=None, *, file=None): nntplib.py: def help(self, *, file=None): nntplib.py: def head(self, message_spec=None, *, file=None): nntplib.py: def body(self, message_spec=None, *, file=None): nntplib.py: def article(self, message_spec=None, *, file=None): nntplib.py: def xhdr(self, hdr, str, *, file=None): nntplib.py: def xover(self, start, end, *, file=None): nntplib.py: def over(self, message_spec, *, file=None): nntplib.py: def xgtitle(self, group, *, file=None): ntpath.py:# See also module 'glob' for expansion of *, ? and [...] in pathnames. numbers.py: *, /, abs(), .conjugate, ==, and !=. os.py: def fwalk(top=".", topdown=True, onerror=None, *, follow_symlinks=False, dir_fd=None): pickle.py: def __init__(self, file, protocol=None, *, fix_imports=True): pickle.py: def __init__(self, file, *, fix_imports=True, pickle.py: Optional keyword arguments are *fix_imports*, *encoding* and pickle.py: *errors*, which are used to control compatiblity support for pickle.py:def _dump(obj, file, protocol=None, *, fix_imports=True): pickle.py:def _dumps(obj, protocol=None, *, fix_imports=True): pickle.py:def _load(file, *, fix_imports=True, encoding="ASCII", errors="strict"): pickle.py:def _loads(s, *, fix_imports=True, encoding="ASCII", errors="strict"): plistlib.py:def load(fp, *, fmt=None, use_builtin_types=True, dict_type=dict): plistlib.py:def loads(value, *, fmt=None, use_builtin_types=True, dict_type=dict): plistlib.py:def dump(value, fp, *, fmt=FMT_XML, sort_keys=True, skipkeys=False): plistlib.py:def dumps(value, *, fmt=FMT_XML, skipkeys=False, sort_keys=True): posixpath.py:# See also module 'glob' for expansion of *, ? and [...] in pathnames. pprint.py:def pprint(object, stream=None, indent=1, width=80, depth=None, *, pprint.py:def pformat(object, indent=1, width=80, depth=None, *, compact=False): pprint.py: def __init__(self, indent=1, width=80, depth=None, stream=None, *, pydoc.py:def browse(port=0, *, open_browser=True): _pyio.py: *opener* with (*file*, *flags*). *opener* must return an open file _pyio.py: """Read up to len(b) bytes into *b*, using at most one system call _pyio.py: object is then obtained by calling opener with (*name*, *flags*). shutil.py:def copyfile(src, dst, *, follow_symlinks=True): shutil.py:def copymode(src, dst, *, follow_symlinks=True): shutil.py: def _copyxattr(src, dst, *, follow_symlinks=True): shutil.py:def copystat(src, dst, *, follow_symlinks=True): shutil.py:def copy(src, dst, *, follow_symlinks=True): shutil.py:def copy2(src, dst, *, follow_symlinks=True): socket.py: def makefile(self, mode="r", buffering=None, *, ssl.py:def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None, ssl.py:def _create_unverified_context(protocol=PROTOCOL_SSLv23, *, cert_reqs=None, tarfile.py: def list(self, verbose=True, *, members=None): tarfile.py: def add(self, name, arcname=None, recursive=True, exclude=None, *, filter=None): tarfile.py: def extractall(self, path=".", members=None, *, numeric_owner=False): tarfile.py: def extract(self, member, path="", set_attrs=True, *, numeric_owner=False): textwrap.py: *, textwrap.py: the *width*, it is returned as is. Otherwise, as many words threading.py: args=(), kwargs=None, *, daemon=None): timeit.py:def main(args=None, *, _wrap_timer=None): traceback.py: def __init__(self, filename, lineno, name, *, lookup_line=True, traceback.py: def extract(klass, frame_gen, *, limit=None, lookup_lines=True, traceback.py: def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, traceback.py: def format(self, *, chain=True): traceback.py: If chain is not *True*, *__cause__* and *__context__* will not be formatted. typing.py: def __new__(cls, name, bases, namespace, *, _root=False): warnings.py: def __init__(self, *, record=False, module=None): -- ~Ethan~

On Jun 17, 2015 12:11 PM, "Amber Yust" <amber.yust@gmail.com> wrote:
Interesting. I don't think I've ever seen it used, even having looked at
Python 3 code. For those who have worked with more Python 3 code than I have, do you ever see it used? Unfortunately, no, because at this point almost all py3 APIs I see are still aiming for py2/py3 compatibility, and there's really no good way to accomplish kw only args in py2. It can be done, but it's very cumbersome; not like range or print or whatever where you can just import something from six or add some parentheses. In retrospect I wish this had been backported to 2.7, because they're super useful for making better APIs, but that ship has sailed. -n

In retrospect I wish this had been backported to 2.7, because they're super useful for making better APIs, but that ship has sailed
Why would this not be able to be ported now? It does not clash with any existing python 2 syntax so all current python 2 is still valid and has no behaviour change. On Wed, Jun 17, 2015 at 3:32 PM, Nathaniel Smith <njs@pobox.com> wrote:

On Wed, Jun 17, 2015 at 3:36 PM, Joseph Jevnik <joejev@gmail.com> wrote:
Python 2.7 is not getting new features (ssl changes notwithstanding), and there will never be a Python 2.8. There's certainly no desire to add new syntax so there would be code that will run in Python 2.7.11 only, and not in earlier 2.7 releases.

On 18 Jun 2015 6:27 am, "Geoffrey Spear" <geoffspear@gmail.com> wrote:
On Wed, Jun 17, 2015 at 3:36 PM, Joseph Jevnik <joejev@gmail.com> wrote:
In retrospect I wish this had been backported to 2.7, because they're
super useful for making better APIs, but that ship has sailed there will never be a Python 2.8. There's certainly no desire to add new syntax so there would be code that will run in Python 2.7.11 only, and not in earlier 2.7 releases. Exactly - it's only in truly exceptional cases like the PEP 466 & 476 network security changes that we'll add features to Python 2.7. Keyword-only arguments are certainly a nice enhancement, but their absence isn't actively harmful the way the aging network security capabilities were. Regards, Nick.

On Wed, 17 Jun 2015 at 21:33 Nathaniel Smith <njs@pobox.com> wrote:
As you correctly point out, it can't be done without friction. I've attempted backporting kw-only parameters through decorators: from sigtools import modifiers @modifiers.kwoargs('kwop') def func(abc, kwop): ... @modifiers.autokwoargs def func(abc, kwop=False): ... http://sigtools.readthedocs.org/en/latest/#sigtools.modifiers.kwoargs

OK, i think it’s time to finally switch to python 3 instead of writing more horrible crutches: # coding: utf-8from __future__ import absolute_import, division, print_function, unicode_literalsfrom builtins import * import trolliusfrom trollius import From, Return from other import stuff @trollius.coroutine@modifiers.kwoargs('b')def awesome_stuff(a, b=5): res = (yield From(stuff())) raise Return(res) vs. import asyncio from other import stuff @asyncio.coroutinedef awesome_stuff(a, *, b=5): res = (yield from stuff()) return res or soon: from other import stuff async def awesome_stuff(a, *, b=5): res = await stuff() return res Yann Kaiser kaiser.yann@gmail.com <http://mailto:kaiser.yann@gmail.com> schrieb am Sa., 20. Juni 2015 um 19:52 Uhr: On Wed, 17 Jun 2015 at 21:33 Nathaniel Smith <njs@pobox.com> wrote:

Definitely agree with wanting to move on and focus on Python 3. I design my stuff with Python 3 first in mind, but reality tells me I need to keep supporting Python 2 users, even if that means uglifying front-page examples with things such as sigtools.modifiers.kwoargs. The existence of this thread and of some of the emails in the "signature documentation" thread only serves as proof that I'd confuse too many by keeping "kwoargs" & co a side-note. Developing on Python 3.4 is great, things like chained tracebacks are fantastic. If I can do it on Python 3, I will. But what if I want to deploy to GAE? Stuck with 2.7. So are many users. It's ugly, it sucks, but so far it is out of my hands and necessary. On Sat, 20 Jun 2015 at 20:13 Philipp A. <flying-sheep@web.de> wrote:

sure! and having those workarounds if you really need them is great. it’s just really frustrating that so many of you are stuck with 2.7 or even less without more reason than “sysadmin won’t install a SCL”. Yann Kaiser <kaiser.yann@gmail.com> schrieb am Sa., 20. Juni 2015 um 20:28 Uhr:

My approach to this particular case has always been, if I need to port keyword-only args to older Python versions, I'll just remove the '*' and make it a documented convention that those arguments must be passed by keyword only, without enforcement. The code obfuscation is just not worth it -- it's just rarely super-important to strictly enforce this convention. -- --Guido van Rossum (python.org/~guido)

On Jun 17, 2015 8:58 PM, "Amber Yust" <amber.yust@gmail.com> wrote:
One thing that has been a source of bugs and frustration in the past is
the inability to designate a named keyword argument that cannot be passed as a positional argument (short of **kwargs and then keying into the dict directly). Has there been any previous discussion on the possibility of a means to designate named arguments as explicitly non-positional?
Not a solid proposal, but to capture the essential difference of what I'm
thinking of, along the lines of...
def foo(bar, baz=None, qux: None):
where bar is a required positional argument, baz is an optional argument
that can have a value passed positionally or by name, and qux is an optional argument that must always be passed by keyword.
Such a means would help avoid cases where a misremembered function
signature results in a subtle and likely unnoticed bug due to unintended parameter/argument mismatch.
(It's possible that this has been discussed before - a cursory search of
python-ideas didn't bring up any direct discussion, but I may have missed something. If you have a link to prior discussion, please by all means point me at it!)
Already present in python 3: https://www.python.org/dev/peps/pep-3102/

Hi Amber, On 06/17/2015 12:58 PM, Amber Yust wrote:
I can do better than prior discussion - this already exists in Python 3: Python 3.4.2 (default, Dec 12 2014, 17:46:08) [GCC 4.8.2] on linux Type "help", "copyright", "credits" or "license" for more information.
participants (12)
-
Amber Yust
-
Carl Meyer
-
Chris Kaynor
-
Ethan Furman
-
Geoffrey Spear
-
Guido van Rossum
-
Joseph Jevnik
-
Nathaniel Smith
-
Nick Coghlan
-
Philipp A.
-
Todd
-
Yann Kaiser