15 ms·
Positional-only Parameters for Python
- alexbecker 8y agoSome people are saying the syntax is ugly, and I agree. But it addresses a real problem in Python: the names of the arguments of your public functions are also part of your public API (since they can be called as kwargs). In my experience, this is rarely desirable and makes fixing bad names unnecessarily hard.
- masklinn 8y agoAnd worse, they cause problems in some APIs e.g. defining / overriding MutableMapping.update(), it should take a single optional positional-only parameter and any number of keyword arguments, currently this requires using *args and hand-rolled assertions, otherwise you can get kwarg conflicts and other such issues.
- js2 8y agoFunction names and class names are also part of your API and there's no facility to allow those to be arbitrarily renamed. Being able to rename positional parameter names is a very weak argument in favor of this feature. The only argument I'm even remotely sympathetic to is "If a function uses a name for a parameter, that precludes callers from using it as a keyword argument elsewhere in the argument list." But the workaround for that rare case is to use: *args, **kwargs
- ak217 8y agoKeyword arguments are safer and more explicit/self-documenting. It's not clear that further accommodations for positional arguments are needed, except in variadic situations as described in https://www.python.org/dev/peps/pep-3102/ https://www.python.org/dev/peps/pep-3102/. I would argue the use case described in the "background" section constitutes an unsafe level of uncertainty. One important use case for the existing facilities is to take a function whose signature used to contain positionals, and migrate a positional to be a keyword argument without breaking existing uses of the API. I think `/` is a poor choice of symbol, too. If such a separator were to be a good idea, it should be * * by analogy with `*` for separating keyword-only args.
- dietrichepp 8y agoI think these arguments usually come down to the domain of problems you work with. Some people simply don’t write code where it makes sense to have positional arguments. Some domains have arguments where positional arguments are more natural/self-documenting. For example, concatenation, mathematical operations.
- Retra 8y agoKeyword arguments also encourage overly general functions that try to do everything, and there's a performance cost to that (such functions tend to include a mini arg parser and dispatch mechanism), and it's a wash on documentation, since looking up how to use such a function really isn't any harder than looking up which of a number of functions to use.
- masklinn 8y ago> Keyword arguments are safer and more explicit/self-documenting. Keyword arguments are also more redundant (a function taking a single argument usually does not need that argument to be named, quite the opposite, even for some multi-parameter functions it makes no sense e.g. `max(a=1, b=2)` is downright no matter how you name the arguments)), and the lack of positional-only parameters means any old name to what was intended as a positional effectively becomes part of the API. I think Swift has the better pick there, an argument can be either positional or named, not both, and positional-only parameters are used to great effect. Sadly Python's history of positional-or-named can't really be fixed.
- ehsankia 8y ago> a function taking a single argument usually does not need that argument to be named Would you rather have: thread_pool.shutdown(True) or thread_pool.shutdown(wait=True) Even with single argument, it's not always clear what the argument means from the function name, so being explicit makes the code more readable. EDIT: I realize you said "usually", but my point still stands.
- totalperspectiv 8y ago
- js2 8y ago> Over time, we’ve had a trend of adding unnecessary, low-payoff complexity to the language. Cumulatively, it has greatly increased the mental load for newcomers and for occasional users. I couldn't agree more with this sentiment from Raymond Hettinger. I first learned Python starting with 1.5.2, and coming from a background of C and Perl. One of the things that really attracted me to the language was how compact it was. It took no more than an hour to learn most of the language, a day to read the entire spec, and maybe a second day to read the entire standard library documentation. It suffered from virtually no gotchas... nothing crazy like Perl's scalar vs list context, etc. I haven't had trouble keeping pace with Python and I even probably agree with most of its changes. But this change really seems superfluous. How does it fit into the zen? Can a language ever reach a point of being done?
- hangonhn 8y agoI feel the same but about Java. I feel that with Java, however, it is much, much worse because of its enterprise friendliness and thus being much more reluctant about breaking changes to the language. I would love to see certain features getting dropped from languages as new ones are added. I think Python has done an okay job of keeping the language consistent and simple and cleaning up the libraries, etc.
- js2 8y agoFunny enough, I first used Java in 1995 or so. What a nice little language I thought then. I then managed to avoid it through its enterprise madness of the 2000s and finally started using it again around 5 years ago. It's still bureaucratic but I don't find the language itself complex.
- red_hare 8y agoYou're not alone. My goto example of this is the pathlib module added in 3.4. It has this feature for making it easier to concat strings representing file system paths... >>> from pathlib import Path >>> Path("foo/bar") / "baz" PosixPath('foo/bar/baz') Don't get me wrong, the existing solutions until pathlib (os.path.join or string concatenation) left a lot to be desired. And there are certain tricky path-parsing things this module does that are wonderful. But overloading the __div__ operator for a "cute" API just feels... wrong... Of course, after my initial disgust, I now use it everywhere :)
- vharuck 8y agoThe arguments for this change undervalue one of my favorite things about Python: developer freedom. The language is versatile to the point of being inefficient, but that lets me adapt it as necessary. Just because they can't imagine a situation where this change would be a roadblock doesn't mean it won't happen. What about building an argument list with a dictionary and then passing it as an expanded dictionary? Also, I don't like the idea that package developers' opinions on what's "readable code" outrank mine, when we're talking about my code.
- r0f1 8y agoIs this a new thing? I thought that was possible in Python already. Look at the numpy documentation: https://docs.scipy.org/doc/numpy/reference/generated/numpy.maximum.html https://docs.scipy.org/doc/numpy/reference/generated/numpy.m.... There, they already use the / for positional-only arguments.
- masklinn 8y ago> Is this a new thing? I thought that was possible in Python already. Yes and no: the syntax was used in documentation (that's the "argument clinic" mentioned in the article) and Python functions defined in C were able to define positional-only parameters, but it's a new thing for Python code: before 3.8, it's not possible to define positional-only parameters in pure Python[0], with it the "argument clinic" syntax becomes actual Python syntax (rather than pseudo-python). [0] well you can use *args and a bunch of hand-rolled assertions
- ram_rar 8y agoThe more and more I see the direction python is taking, the more it feels like python will eventually become the new Perl. I honestly feel, if Python 2.7+ had "fixed" unicode and included orderedDict (which I guess came after 3.5) I would happily stay with it.
- Izkata 8y agoPython 2.7 has it... "from collections import OrderedDict"
- kevin_thibedeau 8y agoIf backward compatibility is a goal, one can't make capricious changes to the order of positional args. It seems pointless to make special provisions to prevent named args for the sake of "more flexibility" when positional is decidedly less flexible. You can always opt for dict args if you want to change a signature and maintain backwards compatibility with obsolescent arg names. If I was BDFL this would be a dead issue.
- deleted 8y ago[deleted]
- verisimilitudes 8y agoI do find having keyword parameters for free, as Ada does, to be an interesting feature, but the need to make the parameter names part of the interface is a drawback. Common Lisp separates positional parameters and keywords, but at the cost of some efficiency and yet other drawbacks. In general, though, I don't see the issue with always needing to pick good names. It's very simple to use single-character names if one needs to, such as for a mathematical function, or simply a procedure where the letters have clear meanings or will likely never be used to start with. Asides from the design features I thought were jokes when I first learned of them, such as one-line lambdas and whatnot, this ''beginner's language'' certainly has a great deal of complicated syntax sugar and other things being added to it.
- xisukar 8y agoIn Raku, parameters can be either positional or named parameters (declared with the colon-pair syntax). However, from what I understand, only positional parameters can be passed as positional and named parameters as named. The closest Raku subroutine to Python's `def fun(a, b, c=None)` would be: sub fun($a, $b, :$c) { } fun(1, 2) # This works! fun(1, 2, 3) # Error: Too many positionals passed... fun(:a(1), :b(2), :c(3)) # Error: Too few positionals passed... fun(:c(3), :a(1), :b(2)) # Same as before fun(:c(3), 1, 2) # This works! Positional parameters are required by default but they can be made optional by providing a default value in the subroutine's signature or by placing a `?` right after the parameter. However, much like Python, positional parameters must appear before optional parameters: sub fun($b, $a?, :$c) { } fun(1) # This works! On the other hand, named parameter, as evidenced by the first code snippet, are optional. However, they can be made required by placing a `!` right after the parameter: sub fun($a, $b, :$c!) { } fun(1, 2) # Error: Required named parameter 'c' not passed fun(1, 2, :c(3)) # This works! Remaining arguments can be slurped by using slurpy parameters. In its most basic form, slurpy positional parameters are indicated with `A` followed by an array parameter: sub fun($a, $b, A@rest) { } However, regardless of the structure of the arguments, they are flatten. This automatic flattening can be avoided by using `AA` instead. Or it can be done conditionally by using `+`: If there's a single argument, flaten it. Else, leave them alone. Slurpy named parameters are indicated also with `A` followed by a hash parameter: sub fun(:$c, :$d, A%rest) { } Both can be used in conjunction: sub fun($a, $b, :$c, :$d, A@rest, A%rest) { } # Or: sub fun($a, $b, A@rest, :$c, :$d, A%rest) { } Edit: HN eats up the asterisks so using `A` to stand for asterisk.
- twic 8y agoYou can already do: def show((a, b, c)): print 'a = %s, b = %s, c = %s' % (a, b, c) And there's no way to pass a, b, and c other than positionally. You can write: show((1, 2, 3)) But not: show((a=1, b=2, c=3))
- rbonvall 8y agoThat feature was removed in python 3: $ python2 >>> def f((a, b)): ... return a + b ...· >>> f((1, 2)) 3 $ python3 >>> def f((a, b)): File "<stdin>", line 1 def f((a, b)): ^ SyntaxError: invalid syntax
- twic 8y agoThis seems pretty nuts. I'm with Steve Dower here - make all parameters allowed as keyword arguments. Guido's objection that "writing len(obj=configurations) is not something we want to encourage" makes not one iota of sense. Allowing it is not encouraging it. There's mention of one bug which allegedly could have been fixed with positional-only parameters: https://bugs.python.org/issue9137 https://bugs.python.org/issue9137 The bug here is that dicts have an update method which you can call two ways, by passing a single dict, or with keyword arguments, and if you try to call the single-dict version using a named parameter, it's not clear what should happen, and at one point, for OrderedDict, it could end up throwing an exception. But the mistake here isn't confusing parameters, it's having a single method that you can call in two different ways. That sort of thing might fly in Perl, but it's a source of ambiguity and confusion that, IMHO, is not up to Python's standards.
- amelius 8y agoThere is a simple solution to this problem. Just name your parameters like this: def fun(parameter1, parameter2, parameter3): a = parameter1 b = parameter2 c = parameter3 ... (rest of function) This also helps the user to see in an eyeblink which position corresponds to which parameter.