Boolean parameters guidelines
I propose to add following recommendations in PEP 8 or other documents: 1. It is preferable to pass boolean arguments as keyword arguments (if this is not the only argument). 2. It is preferable to declare boolean parameters as keyword-only parameters. What are you think about this?
+1: This is by far my biggest use case for keyword only parameters. On Sat, May 7, 2016 at 5:41 PM, Serhiy Storchaka <storchaka@gmail.com> wrote:
I propose to add following recommendations in PEP 8 or other documents:
1. It is preferable to pass boolean arguments as keyword arguments (if this is not the only argument).
2. It is preferable to declare boolean parameters as keyword-only parameters.
What are you think about this?
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Sun, May 08, 2016 at 12:41:45AM +0300, Serhiy Storchaka wrote:
I propose to add following recommendations in PEP 8 or other documents:
1. It is preferable to pass boolean arguments as keyword arguments (if this is not the only argument).
2. It is preferable to declare boolean parameters as keyword-only parameters.
What are you think about this?
I think it is preferable *not* to have boolean parameters at all. I don't remember if this is Guido's name for it, but I remember him expressing the guideline "No constant bool arguments". If you have an argument which takes a bool, and is used solely to switch between two different modes, and the caller will most likely call the function with the argument as a constant known when writing the code (rather than taking an expression or variable with value not known until runtime), then it is usually better to split the function into two, one for each mode. For example, in the statistics module, I could have written: def variance(data, pop=False): ... and have the `pop` argument switch between population variance and sample variance (the default). But that would fail the "No constant bool args" test, since you almost always know ahead of time which variance you want. So instead there are two separate functions, variance and pvariance. Obviously this is a guideline, not a hard rule, like all rules in PEP 8, and there may be exceptions, e.g. the closefd argument to open. So I think that if you are going to write a recommendation for treatment of bool arguments, it should start with "(1) Try to avoid bool args", explain the "No constant bool arguments" principle, and only then go on with "(2) if you still have bool arguments, then use keyword arguments..." as you suggest above. -- Steve
On Sun, May 8, 2016 at 11:59 AM, Steven D'Aprano <steve@pearwood.info> wrote:
Obviously this is a guideline, not a hard rule, like all rules in PEP 8, and there may be exceptions, e.g. the closefd argument to open.
The difference with open() is that it's really a flag on the resulting file. It doesn't do something fundamentally different - it gives back a file object either way, and there's a hidden bit of state that says "hey, closefd was/wasn't set". There's actually a more fundamental difference between open(fn, "rb") and open(fn, "rt"), but for hysterical reasons that's all bound up in the "mode". IMO that would be better done with separate keyword arguments. (Obviously I'm not suggesting changing the existing function, but this is for PEP 8 and general advice.)
So I think that if you are going to write a recommendation for treatment of bool arguments, it should start with "(1) Try to avoid bool args", explain the "No constant bool arguments" principle, and only then go on with "(2) if you still have bool arguments, then use keyword arguments..." as you suggest above.
#2 is particularly applicable if there's a whole matrix of possible options. Taking open() again, we have flag options for closefd, newline, read/write/append/update, and text/binary (these last two being packaged up into the "mode"); and more flags could easily be created in the future, same as closefd was. Creating separate functions for every combination would be ridiculously impractical, ergo the one master function is the cleaner way to do it. ChrisA
On 7 May 2016 at 22:59, Steven D'Aprano <steve@pearwood.info> wrote:
So instead there are two separate functions, variance and pvariance.
Which, to avoid copy and pasting code, would obviously each consist of a call do a _variance private function, passing the operation mode as a ... Boolean parameter. And, if there are three booleans, one should actually write 8 different functions doing the same thing.... So, regardless of Guido or have or not having advised that, I think this is just bogus.
On Sun, May 08, 2016 at 01:58:35AM -0300, Joao S. O. Bueno wrote:
On 7 May 2016 at 22:59, Steven D'Aprano <steve@pearwood.info> wrote:
So instead there are two separate functions, variance and pvariance.
Which, to avoid copy and pasting code, would obviously each consist of a call do a _variance private function, passing the operation mode as a ... Boolean parameter.
You know, the standard library is open source, and the source code is available :-) You could easily check to see whether that is what it does: https://hg.python.org/cpython/file/3.5/Lib/statistics.py Ignoring docstrings, this is the current implementation of the two functions: def variance(data, xbar=None): if iter(data) is data: data = list(data) n = len(data) if n < 2: raise StatisticsError('variance requires at least two data points') T, ss = _ss(data, xbar) return _convert(ss/(n-1), T) def pvariance(data, mu=None): if iter(data) is data: data = list(data) n = len(data) if n < 1: raise StatisticsError('pvariance requires at least one data point') ss = _ss(data, mu) T, ss = _ss(data, mu) return _convert(ss/n, T) So, no, it does not pass a hidden boolean argument. Virtually all the common code (apart from some trivial setup code) is already factored into private subfunctions, which I would have still done even if I had used a bool to set the mode. Even if I did use a private bool arg, I think that exposing it in the public interface would have been the wrong choice. Conceptually, sample variance and population variance are related but distinct things. It makes for a better API to have two distinct functions rather than passing a flag to choose between them. There are cases where using a bool to select a mode makes sense, which is why it is only a guideline, not a hard, unbending law that must always be obeyed, no exceptions. There may be cases where we choose to intentionally disregard the "No const bool args" rule, and that's okay, if we disregard it for good reasons. For example, I think that list.sort(reverse=True) is the right choice, in which case the other rules about using keyword arguments apply. But I think that we should start from a position of "No const bool args" being the preferred option. -- Steve
On May 08, 2016, at 11:59 AM, Steven D'Aprano wrote:
I think it is preferable *not* to have boolean parameters at all.
I don't remember if this is Guido's name for it, but I remember him expressing the guideline "No constant bool arguments". If you have an argument which takes a bool, and is used solely to switch between two different modes, and the caller will most likely call the function with the argument as a constant known when writing the code (rather than taking an expression or variable with value not known until runtime), then it is usually better to split the function into two, one for each mode.
I think Guido's point is stricter than that (but I don't want to put words in his mouth :). It's that a boolean flag argument shouldn't change the return type of the method. Flags can certainly change the behavior of a method and I think that's perfect fine. Cheers, -Barry
On Mon, May 9, 2016 at 12:36 PM, Barry Warsaw <barry@python.org> wrote:
On May 08, 2016, at 11:59 AM, Steven D'Aprano wrote:
I think it is preferable *not* to have boolean parameters at all.
I don't remember if this is Guido's name for it, but I remember him expressing the guideline "No constant bool arguments". If you have an argument which takes a bool, and is used solely to switch between two different modes, and the caller will most likely call the function with the argument as a constant known when writing the code (rather than taking an expression or variable with value not known until runtime), then it is usually better to split the function into two, one for each mode.
I think Guido's point is stricter than that (but I don't want to put words in his mouth :). It's that a boolean flag argument shouldn't change the return type of the method. Flags can certainly change the behavior of a method and I think that's perfect fine.
These are two different things. Any time the *value* of an argument affects the *type* of the return value there's a problem, not just for boolean parameters. E.g. open(..., "rb") vs. open(..., "r") is one of the worst offenders -- and "r" vs. "w" is another one for open()! The thing Steven quotes is specific to boolean parameters and pretty much exactly what I would say about them. -- --Guido van Rossum (python.org/~guido)
On 09.05.2016 21:47, Guido van Rossum wrote:
On Mon, May 9, 2016 at 12:36 PM, Barry Warsaw <barry@python.org> wrote:
On May 08, 2016, at 11:59 AM, Steven D'Aprano wrote:
I think it is preferable *not* to have boolean parameters at all.
I don't remember if this is Guido's name for it, but I remember him expressing the guideline "No constant bool arguments". If you have an argument which takes a bool, and is used solely to switch between two different modes, and the caller will most likely call the function with the argument as a constant known when writing the code (rather than taking an expression or variable with value not known until runtime), then it is usually better to split the function into two, one for each mode.
I think Guido's point is stricter than that (but I don't want to put words in his mouth :). It's that a boolean flag argument shouldn't change the return type of the method. Flags can certainly change the behavior of a method and I think that's perfect fine.
These are two different things. Any time the *value* of an argument affects the *type* of the return value there's a problem, not just for boolean parameters. E.g. open(..., "rb") vs. open(..., "r") is one of the worst offenders -- and "r" vs. "w" is another one for open()!
The thing Steven quotes is specific to boolean parameters and pretty much exactly what I would say about them.
This seems overly strict, e.g. it's not uncommon to have functions enable debugging, verbose processing or similar processing variants using a boolean parameter. -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, May 09 2016)
Python Projects, Coaching and Consulting ... http://www.egenix.com/ Python Database Interfaces ... http://products.egenix.com/ Plone/Zope Database Interfaces ... http://zope.egenix.com/
::: We implement business ideas - efficiently in both time and costs ::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/ http://www.malemburg.com/
On Mon, May 9, 2016 at 1:00 PM, M.-A. Lemburg <mal@egenix.com> wrote:
On 09.05.2016 21:47, Guido van Rossum wrote:
On Mon, May 9, 2016 at 12:36 PM, Barry Warsaw <barry@python.org> wrote:
On May 08, 2016, at 11:59 AM, Steven D'Aprano wrote:
I think it is preferable *not* to have boolean parameters at all.
I don't remember if this is Guido's name for it, but I remember him expressing the guideline "No constant bool arguments". If you have an argument which takes a bool, and is used solely to switch between two different modes, and the caller will most likely call the function with the argument as a constant known when writing the code (rather than taking an expression or variable with value not known until runtime), then it is usually better to split the function into two, one for each mode.
This seems overly strict, e.g. it's not uncommon to have functions enable debugging, verbose processing or similar processing variants using a boolean parameter.
But usually the call site values for those wouldn't be constant -- they'd be computed from a command line flag for example. The key part of the phrasing is "the caller will most likely call the function with the argument as a constant known when writing the code". FWIW I disagree with the lead-in phrase "I think it is preferable *not* to have boolean parameters at all." -- --Guido van Rossum (python.org/~guido)
On 09.05.2016 22:07, Guido van Rossum wrote:
On Mon, May 9, 2016 at 1:00 PM, M.-A. Lemburg <mal@egenix.com> wrote:
On 09.05.2016 21:47, Guido van Rossum wrote:
On Mon, May 9, 2016 at 12:36 PM, Barry Warsaw <barry@python.org> wrote:
On May 08, 2016, at 11:59 AM, Steven D'Aprano wrote:
I think it is preferable *not* to have boolean parameters at all.
I don't remember if this is Guido's name for it, but I remember him expressing the guideline "No constant bool arguments". If you have an argument which takes a bool, and is used solely to switch between two different modes, and the caller will most likely call the function with the argument as a constant known when writing the code (rather than taking an expression or variable with value not known until runtime), then it is usually better to split the function into two, one for each mode.
This seems overly strict, e.g. it's not uncommon to have functions enable debugging, verbose processing or similar processing variants using a boolean parameter.
But usually the call site values for those wouldn't be constant -- they'd be computed from a command line flag for example. The key part of the phrasing is "the caller will most likely call the function with the argument as a constant known when writing the code".
Hmm, so if you'd typically pass in a constant for the parameter it's deemed poor style, whereas when the value comes from some variable, it's fine ? This looks more like an API design question than a coding style one. E.g. take this example: def process(data, raise_errors=True): try: ... return result except ValueError: if raise_errors: raise else: return None It's not clear whether the caller would run the function with raise_errors=True or raise_errors=config.raise_errors more often.
FWIW I disagree with the lead-in phrase "I think it is preferable *not* to have boolean parameters at all."
Same here. -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, May 09 2016)
Python Projects, Coaching and Consulting ... http://www.egenix.com/ Python Database Interfaces ... http://products.egenix.com/ Plone/Zope Database Interfaces ... http://zope.egenix.com/
::: We implement business ideas - efficiently in both time and costs ::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/ http://www.malemburg.com/
On Mon, May 9, 2016 at 2:57 PM, M.-A. Lemburg <mal@egenix.com> wrote:
Hmm, so if you'd typically pass in a constant for the parameter it's deemed poor style, whereas when the value comes from some variable, it's fine ?
Yes.
This looks more like an API design question than a coding style one.
Yes. IIRC the question that started this thread was about API design guidance.
E.g. take this example:
def process(data, raise_errors=True): try: ... return result except ValueError: if raise_errors: raise else: return None
It's not clear whether the caller would run the function with raise_errors=True or raise_errors=config.raise_errors more often.
Yeah, that's for the API designer to guess based on how they envision the API to be used. It's often pretty clear once you start writing some example code though. -- --Guido van Rossum (python.org/~guido)
On 10 May 2016 at 07:57, M.-A. Lemburg <mal@egenix.com> wrote:
This looks more like an API design question than a coding style one.
This is actually a good point - at the moment, PEP 8 is a mix of coding style guidelines (i.e. how we use existing APIs) and API design guidelines (especially the parts on naming conventions and defining what is and isn't a public API). For code reviews, we want the former, but for PEP discussions we want the latter. Perhaps it would make sense to give API design guidelines a dedicated home in the developer guide, rather than overloading PEP 8 with them? Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On May 10, 2016, at 11:29 PM, Nick Coghlan wrote:
This is actually a good point - at the moment, PEP 8 is a mix of coding style guidelines (i.e. how we use existing APIs) and API design guidelines (especially the parts on naming conventions and defining what is and isn't a public API).
For code reviews, we want the former, but for PEP discussions we want the latter. Perhaps it would make sense to give API design guidelines a dedicated home in the developer guide, rather than overloading PEP 8 with them?
PEP 8 is a *style guide*. Cheers, -Barry
On Tue, May 10, 2016 at 09:34:56AM -0400, Barry Warsaw wrote:
On May 10, 2016, at 11:29 PM, Nick Coghlan wrote:
This is actually a good point - at the moment, PEP 8 is a mix of coding style guidelines (i.e. how we use existing APIs) and API design guidelines (especially the parts on naming conventions and defining what is and isn't a public API).
For code reviews, we want the former, but for PEP discussions we want the latter. Perhaps it would make sense to give API design guidelines a dedicated home in the developer guide, rather than overloading PEP 8 with them?
PEP 8 is a *style guide*.
Surely if we are discussing what PEP 8 *is*, then it is what Nick describes: a mix of coding style guidelines and API design guidelines. If people wish to separate the two into separate documents, that's one thing, but regardless of what PEP 8 claims to be, it does contain both style and API guides. Right now, that's what it is, whether that's what it was intended to be or not. (Personally, I think that coding style and API design overlap and that it is pointless to try to separate them. Coding style isn't only how to name your variables, but also includes whether you use map versus list comprehensions, whether you raise exceptions or return error codes, whether you write in a Java-influenced Design Pattern style or a functional style, when to use bool arguments and when not to.) -- Steve
On Tue, May 10, 2016 at 11:48 PM, Steven D'Aprano <steve@pearwood.info> wrote:
(Personally, I think that coding style and API design overlap and that it is pointless to try to separate them. Coding style isn't only how to name your variables, but also includes whether you use map versus list comprehensions, whether you raise exceptions or return error codes, whether you write in a Java-influenced Design Pattern style or a functional style, when to use bool arguments and when not to.)
If the API design guidelines get broken out, they should be closely associated with PEP 8. Maybe call it PEP 18, and have some references in PEP 8? But I'd rather see it all as one cohesive document - advice on how to structure your code to be Pythonic and maintainable. ChrisA
On Tue, May 10, 2016 at 4:53 PM, Chris Angelico <rosuav@gmail.com> wrote:
On Tue, May 10, 2016 at 11:48 PM, Steven D'Aprano <steve@pearwood.info> wrote:
(Personally, I think that coding style and API design overlap and that it is pointless to try to separate them. Coding style isn't only how to name your variables, but also includes whether you use map versus list comprehensions, whether you raise exceptions or return error codes, whether you write in a Java-influenced Design Pattern style or a functional style, when to use bool arguments and when not to.)
If the API design guidelines get broken out, they should be closely associated with PEP 8. Maybe call it PEP 18, and have some references in PEP 8? But I'd rather see it all as one cohesive document - advice on how to structure your code to be Pythonic and maintainable.
One advantage of "PEP 18" might be that many style issues don't affect API design. And API design is more critical than style within the implementation, because the former can be very difficult to improve afterwards. And a shorter document may be easier to maintain in a self-consistent manner and to understand by the reader. -- Koos
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On May 10, 2016, at 05:08 PM, Koos Zevenhoven wrote:
One advantage of "PEP 18" might be that many style issues don't affect API design. And API design is more critical than style within the implementation, because the former can be very difficult to improve afterwards. And a shorter document may be easier to maintain in a self-consistent manner and to understand by the reader.
Let's be specific about PEP 8. What is "style" and what is "API design"? IMHO: * Code lay-out: style * String Quotes: style * Whitespace in Expressions and Statements: style * Comments: style * Version Bookkeeping: style * Naming Conventions: style & API intertwined, perhaps[1] - Designing for inheritance: API[2] - Public and internal interfaces: API[2] * Programming Recommendations: style[3] - Function Annotations: style[4] In short, I think style issues are those that you could imagine a tool enforcing <wink>, while API design issues are more for when humans communicate intent to each other. That's not a hard-and-fast rule, but I think it gets pretty close. So really, the major of PEP 8 does belong in a style guide. I'm not sure whether it's better to separate out the bits of API recommendations into a separate PEP, rather than perhaps reorganize or simplify PEP 8. [1] But I would argue that the naming conventions section is more aligned with style and *if* a PEP 8 split were to occur, the majority of this section would stay in PEP 8. [2] With a dash of style. [3] With a pinch of API [4] I put this in the style category because it doesn't make recommendations to *use* function annotations. Cheers, -Barry
On Tue, May 10, 2016 at 8:38 AM, Barry Warsaw <barry@python.org> wrote:
On May 10, 2016, at 05:08 PM, Koos Zevenhoven wrote:
One advantage of "PEP 18" might be that many style issues don't affect API design. And API design is more critical than style within the implementation, because the former can be very difficult to improve afterwards. And a shorter document may be easier to maintain in a self-consistent manner and to understand by the reader.
Let's be specific about PEP 8. What is "style" and what is "API design"? IMHO:
* Code lay-out: style * String Quotes: style * Whitespace in Expressions and Statements: style * Comments: style * Version Bookkeeping: style * Naming Conventions: style & API intertwined, perhaps[1] - Designing for inheritance: API[2] - Public and internal interfaces: API[2] * Programming Recommendations: style[3] - Function Annotations: style[4]
In short, I think style issues are those that you could imagine a tool enforcing <wink>, while API design issues are more for when humans communicate intent to each other. That's not a hard-and-fast rule, but I think it gets pretty close.
I don't like that distinction, wink or no wink. It's not about whether you can mechanically enforce it (this is why I am pushing for the rename of the pep8 tool). E.g. how would the tool know whether a particular name is meant to be private? I think style issues are stuff that makes all code more readable. Naming conventions definitely fall in that category (they hint at what kind of thing something is when you see it used without having to look up the definition right away). I actually agree with your categorizations -- we can discuss inheritance vs. composition until the cows see blue in the face, but it's just much more complex, and it doesn't affect readability -- it affects what happens when large systems built out of diverse components evolve.
So really, the major of PEP 8 does belong in a style guide. I'm not sure whether it's better to separate out the bits of API recommendations into a separate PEP, rather than perhaps reorganize or simplify PEP 8.
I vote for keeping it together but I also vote to keep the more complex API design issues out of the PEP series altogether. Maybe 10 years from now there's a bunch of blog posts with sufficiently broad acceptance that we can add it to the PEPs, but until then I think it's pointless to try and pretend there's much of a standard there (given that even PEP 8 has some controversial bits and occasionally changes). Until then I recommend that everyone watches Josh Bloch's talk on API design yearly. ( https://www.youtube.com/watch?v=heh4OeB9A-c; you can find other versions and slides via this search; https://www.google.com/search?q=josh+bloch+api+design&ie=utf-8&oe=utf-8)
[1] But I would argue that the naming conventions section is more aligned with style and *if* a PEP 8 split were to occur, the majority of this section would stay in PEP 8.
[2] With a dash of style.
[3] With a pinch of API
[4] I put this in the style category because it doesn't make recommendations to *use* function annotations.
This feels like unnecessary use of footnotes... -- --Guido van Rossum (python.org/~guido)
On Tue, May 10, 2016 at 6:29 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 10 May 2016 at 07:57, M.-A. Lemburg <mal@egenix.com> wrote:
This looks more like an API design question than a coding style one.
This is actually a good point - at the moment, PEP 8 is a mix of coding style guidelines (i.e. how we use existing APIs) and API design guidelines (especially the parts on naming conventions and defining what is and isn't a public API).
For code reviews, we want the former, but for PEP discussions we want the latter. Perhaps it would make sense to give API design guidelines a dedicated home in the developer guide, rather than overloading PEP 8 with them?
That seems a short-sighted approach to code reviews. At least where I work, early code review cycles are a great place for API design critique. I do agree that most API design guidelines are too complex to state categorically in a style PEP. The discussion and misunderstanding about the simple rule for boolean parameters is an example of how subtle the distinctions are. So API design is more a subject for blogs and books than for a PEP. -- --Guido van Rossum (python.org/~guido)
On 8 May 2016 at 07:41, Serhiy Storchaka <storchaka@gmail.com> wrote:
I propose to add following recommendations in PEP 8 or other documents:
1. It is preferable to pass boolean arguments as keyword arguments (if this is not the only argument).
2. It is preferable to declare boolean parameters as keyword-only parameters.
What are you think about this?
With Steven's suggestion to also articulate the "Would two functions be better?" question, I think it's a good idea. Suggested points to note: 1. Do not use a boolean toggle to change the return type of a function, define two different functions (e.g. os.walk vs os.fwalk, os.getcwd vs os.getcwdb) 2. If the boolean toggle is expected to be constant for any given call-site, consider defining two different functions (e.g. statistics.variance vs statistics.pvariance) 3. If a boolean toggle is deemed appropriate, it is preferable to make it keyword-only so call sites are always self-documenting 4. When calling a function that accepts a boolean toggle, it is preferable to pass it by keyword to make the call more self-documenting The reason I think this is worth documenting is that it comes up regularly in PEPs that require some associated API design, and these are the rules of thumb we generally apply. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On 5/8/2016 8:14 AM, Nick Coghlan wrote: ...
2. If the boolean toggle is expected to be constant for any given call-site, consider defining two different functions (e.g. statistics.variance vs statistics.pvariance)
Wouldn't that be "for every (or almost every) call-site"? Surely a few constants here and there are okay, it's when parameters are constants almost everywhere that the guidance comes in to play.
The reason I think this is worth documenting is that it comes up regularly in PEPs that require some associated API design, and these are the rules of thumb we generally apply.
Agreed. Eric.
On 8 May 2016 at 23:08, Eric V. Smith <eric@trueblade.com> wrote:
On 5/8/2016 8:14 AM, Nick Coghlan wrote: ...
2. If the boolean toggle is expected to be constant for any given call-site, consider defining two different functions (e.g. statistics.variance vs statistics.pvariance)
Wouldn't that be "for every (or almost every) call-site"? Surely a few constants here and there are okay, it's when parameters are constants almost everywhere that the guidance comes in to play.
Aye, that's a better way of phrasing it (it's what I meant, but I can see how "any given" could be read as "at least one", rather than "if you select a call site at random, it will be using a constant value"). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Sun, May 8, 2016 at 6:08 AM, Eric V. Smith <eric@trueblade.com> wrote:
Wouldn't that be "for every (or almost every) call-site"?
"most call sites" would be fine. The common use case matters, it's not that hard to conditionally call a different function, as long as that's the uncommon use-case. The other key here is that even if a boolean flag does essentially select between two different function, there are times where there is more than one such boolean flag for a single function, so you end up with 2**n "different" functions -- in this case, probably better to just use the flags and have one calling point. -CHB -- Christopher Barker, Ph.D. Oceanographer Emergency Response Division NOAA/NOS/OR&R (206) 526-6959 voice 7600 Sand Point Way NE (206) 526-6329 fax Seattle, WA 98115 (206) 526-6317 main reception Chris.Barker@noaa.gov
+1 on Steven D'Aprano's suggestion (And thus *Nick's* too). I have seen *way* too many complicated functions with lots of booleans that create an ugly 20 lines function call. I agree that splitting into 2 if possible is a good idea, and in the cases it's not, requesting keyword only arguments is a great solution. I do think though that in case of many possible options, using IntEnums and OR operators is the preferable method, unlike open() that uses a string, but that's again just a private opinion. Huge +1. On Mon, May 9, 2016 at 8:58 PM Chris Barker <chris.barker@noaa.gov> wrote:
On Sun, May 8, 2016 at 6:08 AM, Eric V. Smith <eric@trueblade.com> wrote:
Wouldn't that be "for every (or almost every) call-site"?
"most call sites" would be fine. The common use case matters, it's not that hard to conditionally call a different function, as long as that's the uncommon use-case.
The other key here is that even if a boolean flag does essentially select between two different function, there are times where there is more than one such boolean flag for a single function, so you end up with 2**n "different" functions -- in this case, probably better to just use the flags and have one calling point.
-CHB
--
Christopher Barker, Ph.D. Oceanographer
Emergency Response Division NOAA/NOS/OR&R (206) 526-6959 voice 7600 Sand Point Way NE (206) 526-6329 fax Seattle, WA 98115 (206) 526-6317 main reception
Chris.Barker@noaa.gov _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Let's just all remember that open() is a huge antipattern. Having a return type that depends on the value of an argument is terrible, and open() has two flags smushed into a single string (binary mode and read/write/both). -- --Guido van Rossum (python.org/~guido)
On Sun, May 8, 2016 at 3:14 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 8 May 2016 at 07:41, Serhiy Storchaka <storchaka@gmail.com> wrote:
I propose to add following recommendations in PEP 8 or other documents:
1. It is preferable to pass boolean arguments as keyword arguments (if this is not the only argument).
2. It is preferable to declare boolean parameters as keyword-only parameters.
What are you think about this?
With Steven's suggestion to also articulate the "Would two functions be better?" question, I think it's a good idea.
Suggested points to note:
1. Do not use a boolean toggle to change the return type of a function, define two different functions (e.g. os.walk vs os.fwalk, os.getcwd vs os.getcwdb) 2. If the boolean toggle is expected to be constant for any given call-site, consider defining two different functions (e.g. statistics.variance vs statistics.pvariance) 3. If a boolean toggle is deemed appropriate, it is preferable to make it keyword-only so call sites are always self-documenting 4. When calling a function that accepts a boolean toggle, it is preferable to pass it by keyword to make the call more self-documenting
The reason I think this is worth documenting is that it comes up regularly in PEPs that require some associated API design, and these are the rules of thumb we generally apply.
Regarding 3 and 4 above: More generally, would it not be a good thing to always consider using a keyword-only argument instead of a regular keyword argument (argument with a default value)? When using keyword-only arguments: A. It is possible to later turn a keyword-only argument into a regular (keyword) argument without breaking backwards compatibility of the API. or B. It is possible to add optional *positional* arguments to the function later without breaking the API. C. If the keyword-only argument later becomes redundant, it is possible to swallow it in **kwargs and make it effectively disappear (well, almost). -- Koos
Cheers, Nick.
-- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On May 08, 2016, at 12:41 AM, Serhiy Storchaka wrote:
1. It is preferable to pass boolean arguments as keyword arguments (if this is not the only argument).
Even if it is!
2. It is preferable to declare boolean parameters as keyword-only parameters.
Yes, unless the code needs to be bilingual. Cheers, -Barry
Serhiy Storchaka <storchaka@...> writes:
1. It is preferable to pass boolean arguments as keyword arguments (if this is not the only argument).
2. It is preferable to declare boolean parameters as keyword-only parameters.
It is true for literal keywords arguments, but passing a named variable with a boolean value can be fine. # Bad my_func(True) # Good my_func(enable=True) # Good enable=True my_func(enable) # Meh enable=True my_func(enable=enable) # Bad w=True my_func(w)
On Tue, May 10, 2016 at 6:15 AM, Joseph Martinot-Lagarde < contrebasse@gmail.com> wrote:
Serhiy Storchaka <storchaka@...> writes:
1. It is preferable to pass boolean arguments as keyword arguments (if this is not the only argument).
2. It is preferable to declare boolean parameters as keyword-only parameters.
It is true for literal keywords arguments, but passing a named variable with a boolean value can be fine.
# Bad my_func(True)
# Good my_func(enable=True)
# Good enable=True my_func(enable)
# Meh enable=True my_func(enable=enable)
# Bad w=True my_func(w)
That list of examples seems to be aligned along the wrong axis. -- --Guido van Rossum (python.org/~guido)
That list of examples seems to be aligned along the wrong axis.
I don't understand what you mean. The examples should go from Good to Bad or something like that ? Or did I miss the point ? I just wanted to show that requiring a keyword for readability isn't always necessary. It can be up to the user to choose what is the most readable.
I meant that I disagreed in several cases with whether an example was Good or Bad and I couldn't figure out what rule you were using in your head to decide on Good/Bad, IOW what rule you were illustrating with these examples. On Tue, May 10, 2016 at 4:41 PM, Joseph Martinot-Lagarde < contrebasse@gmail.com> wrote:
That list of examples seems to be aligned along the wrong axis.
I don't understand what you mean. The examples should go from Good to Bad or something like that ? Or did I miss the point ?
I just wanted to show that requiring a keyword for readability isn't always necessary. It can be up to the user to choose what is the most readable.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido)
I meant that I disagreed in several cases with whether an example was Good or Bad and I couldn't figure out what rule you were using in your head to decide on Good/Bad, IOW what rule you were illustrating with these examples.
Alright, I'll add some explanation. I took an example already used in this thread to be more clear. # Bad: no indication on what the boolean argument does. process(data, True) # Good: the boolean is used to indicate that errors are raised. process(data, raise_errors=True) # Good: the variable name is used to indicate what the argument means. raise_errors=True process(data, raise_errors) # Meh: both the keyword and the variable name are used to indicate what the argument means. It's not really bad, but it adds no information while adding redundancy. This would happend if the keyword is required. raise_errors=True process(data, raise_errors=raise_errors) # Bad: a variable name is used but it doesn't indicate anything on its meaning. w=True process(data, w)
On Tue, May 10, 2016 at 4:59 PM, Joseph Martinot-Lagarde < contrebasse@gmail.com> wrote:
I meant that I disagreed in several cases with whether an example was Good or Bad and I couldn't figure out what rule you were using in your head to decide on Good/Bad, IOW what rule you were illustrating with these examples.
Alright, I'll add some explanation. I took an example already used in this thread to be more clear.
# Bad: no indication on what the boolean argument does. process(data, True)
# Good: the boolean is used to indicate that errors are raised. process(data, raise_errors=True)
So far so good.
# Good: the variable name is used to indicate what the argument means. raise_errors=True process(data, raise_errors)
Not so sure. If the variable raise_errors were set to False the process() call could seem misleading.
# Meh: both the keyword and the variable name are used to indicate what the argument means. It's not really bad, but it adds no information while adding redundancy. This would happend if the keyword is required. raise_errors=True process(data, raise_errors=raise_errors)
That's actually fine with me; there's often a reason to give the variable another name anyways.
# Bad: a variable name is used but it doesn't indicate anything on its meaning. w=True process(data, w)
All depends. In any case the discussion was more about how to design the API in the first place than about what the call should look like. In particular, if the raise_errors parameter is nearly always given as the literal True or False, it might be better to have process(data) and process_raising_errors(data) -- --Guido van Rossum (python.org/~guido)
On 11.05.2016 02:20, Guido van Rossum wrote:
In any case the discussion was more about how to design the API in the first place than about what the call should look like. In particular, if the raise_errors parameter is nearly always given as the literal True or False, it might be better to have
process(data)
and
process_raising_errors(data)
This would work in the simple case where the try-except is used on the whole inner processing block (as in the example I posted), but in practice the variant you want to make selectable often requires adjusting multiple code sections or paths in the block. By having two separate functions, you'd then create a lot of duplicated code (with all the possibly issues that go with it, e.g. copy&paste errors, bugs only fixed in one copy, poor maintainability, etc.). Example: def complex_process(data, raise_errors=True): try: ...phase 1... except ValueError: if raise_errors: raise else: return None ...phase 2... try: ...phase3... except TypeError: if raise_errors: raise else: return None return result The obvious solution would be to wrap the raising variant in an outer function which catches the exceptions, but in the above example, you'd then have to catch both ValueError and TypeError, which may actually catch more errors than you really want to catch. -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, May 11 2016)
Python Projects, Coaching and Consulting ... http://www.egenix.com/ Python Database Interfaces ... http://products.egenix.com/ Plone/Zope Database Interfaces ... http://zope.egenix.com/
::: We implement business ideas - efficiently in both time and costs ::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/ http://www.malemburg.com/
On 07.05.2016 23:41, Serhiy Storchaka wrote:
I propose to add following recommendations in PEP 8 or other documents:
1. It is preferable to pass boolean arguments as keyword arguments (if this is not the only argument).
2. It is preferable to declare boolean parameters as keyword-only parameters.
As this proposal concerns the readability of code, the situation is arguably worse for boolean parameters; so +1. Additionally, it seems like this could be said for other primitive datatypes as well:
sleep(100) # minutes?
Sven
On Wed, May 11, 2016 at 5:10 PM, Sven R. Kunze <srkunze@mail.de> wrote:
Additionally, it seems like this could be said for other primitive datatypes as well:
sleep(100) # minutes?
Don't be silly. Why on earth would that argument represent minutes? It's days, for consistency with `timedelta(100)`. Mark
On Wed, May 11, 2016 at 12:51 PM, Mark Dickinson <dickinsm@gmail.com> wrote:
On Wed, May 11, 2016 at 5:10 PM, Sven R. Kunze <srkunze@mail.de> wrote:
Additionally, it seems like this could be said for other primitive datatypes as well:
sleep(100) # minutes?
Don't be silly. Why on earth would that argument represent minutes?
It's days, for consistency with `timedelta(100)`.
Still best to be explicit:
sleep(timedelta(100))
It would be awesome if that actually worked.
On Wed, May 11, 2016, at 16:36, Ian Kelly wrote:
sleep(timedelta(100))
It would be awesome if that actually worked.
Well, the recent __fspath__ proposal suggests a solution: How about a method for timedelta-like objects to return a float number of seconds? May also want a way to have datetime-like objects return a timestamp for e.g. utime.
On Wed, May 11, 2016 at 3:36 PM, Ian Kelly <ian.g.kelly@gmail.com> wrote:
On Wed, May 11, 2016 at 12:51 PM, Mark Dickinson <dickinsm@gmail.com> wrote:
On Wed, May 11, 2016 at 5:10 PM, Sven R. Kunze <srkunze@mail.de> wrote:
Additionally, it seems like this could be said for other primitive datatypes as well:
sleep(100) # minutes?
Don't be silly. Why on earth would that argument represent minutes?
It's days, for consistency with `timedelta(100)`.
Still best to be explicit:
sleep(timedelta(100))
It would be awesome if that actually worked.
You just weren't explicit enough:
sleep(timedelta(100).total_seconds())
This is drifting pretty far from the topic at hand, though. -- Zach
On 05/11/2016 02:40 PM, Zachary Ware wrote:
On Wed, May 11, 2016 at 3:36 PM, Ian Kelly wrote:
On Wed, May 11, 2016 at 12:51 PM, Mark Dickinson wrote:
On Wed, May 11, 2016 at 5:10 PM, Sven R. Kunze wrote:
Additionally, it seems like this could be said for other primitive datatypes as well:
--> sleep(100) # minutes?
Don't be silly. Why on earth would that argument represent minutes?
It's days, for consistency with `timedelta(100)`.
Still best to be explicit:
--> sleep(timedelta(100))
It would be awesome if that actually worked.
You just weren't explicit enough:
--> sleep(timedelta(100).total_seconds())
This is drifting pretty far from the topic at hand, though.
There was a topic at hand? Oh, yeah, keyword arguments! Actually, it's very apropos: each of those examples would have been either obviously correct or obviously wrong if the parameter names had been used. -- ~Ethan~
On Thu, May 12, 2016 at 7:57 AM, Ethan Furman <ethan@stoneleaf.us> wrote:
On 05/11/2016 02:40 PM, Zachary Ware wrote:
On Wed, May 11, 2016 at 3:36 PM, Ian Kelly wrote:
On Wed, May 11, 2016 at 12:51 PM, Mark Dickinson wrote:
On Wed, May 11, 2016 at 5:10 PM, Sven R. Kunze wrote:
Additionally, it seems like this could be said for other primitive datatypes as well:
--> sleep(100) # minutes?
Don't be silly. Why on earth would that argument represent minutes?
It's days, for consistency with `timedelta(100)`.
Still best to be explicit:
--> sleep(timedelta(100))
It would be awesome if that actually worked.
You just weren't explicit enough:
--> sleep(timedelta(100).total_seconds())
This is drifting pretty far from the topic at hand, though.
There was a topic at hand?
Oh, yeah, keyword arguments!
Actually, it's very apropos: each of those examples would have been either obviously correct or obviously wrong if the parameter names had been used.
Unfortunately it isn't always possible. rosuav@sikorsky:~$ PAGER=cat python3 Python 3.6.0a0 (default:98678738b7e9, May 2 2016, 13:37:04) [GCC 5.3.1 20160409] on linux Type "help", "copyright", "credits" or "license" for more information.
from time import sleep help(sleep) Help on built-in function sleep in module time:
sleep(...) sleep(seconds) Delay execution for a given number of seconds. The argument may be a floating point number for subsecond precision.
sleep(seconds=1) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: sleep() takes no keyword arguments
So style guides can't really recommend "always use keyword arguments", as there are too many functions implemented in C that don't take any keyword args. They're effectively implemented as: def sleep(*args): [seconds] = args This particular example might be worth using a keyword arg with, except that the confusion isn't within Python, it's across languages. And then there's the parallel question of whether or not fractional seconds are valid. A quick survey of sleep functions shows up: * C on POSIX: integer seconds (usleep and nanosleep for integer micro/nanoseconds) * C on Windows: integer milliseconds * Pike and Python: int or float seconds * REXX on OS/2: integer seconds [syssleep], milliseconds [rxsleep], or decimal seconds So if you're doing cross-language work, you'll need to check your docs anyway, and if you're not, it shouldn't be too hard to remember that sleep() in Python takes a number of seconds. (It's easy if you give it a non-integer value. You might see sleep(1000) and interpret that as milliseconds, but you would never see sleep(0.5) and mistake it for anything other than half a second.) ChrisA
On 5/11/2016 7:48 PM, Chris Angelico wrote:
* C on POSIX: integer seconds (usleep and nanosleep for integer micro/nanoseconds) * C on Windows: integer milliseconds
Ditto for tcl/tk 'sleep' function: widget.after(ms, func, *args)
* Pike and Python: int or float seconds * REXX on OS/2: integer seconds [syssleep], milliseconds [rxsleep], or decimal seconds
So if you're doing cross-language work, you'll need to check your docs anyway,
Moving from sleep(seconds) to .after(ms) is a nuisance that sometimes trips up SO questioners, but it is near the bottom of the list of newbie problems with tkinter. -- Terry Jan Reedy
On 12.05.2016 01:48, Chris Angelico wrote:
So style guides can't really recommend "always use keyword arguments", as there are too many functions implemented in C that don't take any keyword args.
I don't think "sometimes technically impossible" prevents a recommendation. Do-it-when-possible-otherwise-don't suffices here. The same holds for boolean values. Sven
On 12.05.16 02:48, Chris Angelico wrote:
from time import sleep help(sleep) Help on built-in function sleep in module time:
sleep(...) sleep(seconds)
Delay execution for a given number of seconds. The argument may be a floating point number for subsecond precision.
sleep(seconds=1) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: sleep() takes no keyword arguments
Note that passing positional arguments is much faster than passing keyword arguments even if the function supports them. And the difference can be even larger after pushing Victor's optimization. [1] Other optimizations (like [2]) can be applied only for functions that support only positional parameters. Thus passing positional arguments and having functions that support only positional parameters is important for performance. [1] http://bugs.python.org/issue26814 [2] http://bugs.python.org/issue23867
On May 12, 2016 4:29 AM, "Serhiy Storchaka" <storchaka@gmail.com> wrote:
On 12.05.16 02:48, Chris Angelico wrote:
from time import sleep help(sleep)
Help on built-in function sleep in module time:
sleep(...) sleep(seconds)
Delay execution for a given number of seconds. The argument may be a floating point number for subsecond precision.
sleep(seconds=1)
Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: sleep() takes no keyword arguments
Note that passing positional arguments is much faster than passing
Other optimizations (like [2]) can be applied only for functions that support only positional parameters. Thus passing positional arguments and having functions that support only positional parameters is important for
keyword arguments even if the function supports them. And the difference can be even larger after pushing Victor's optimization. [1] performance.
[1] http://bugs.python.org/issue26814 [2] http://bugs.python.org/issue23867
Worth noting that a guarding optimizer, or a "hinted" optimizer (like a decorator on the caller which locks in specified names for functions), can theoretically optimize away those keyword args to positional args. So if performance is important, there could be a future where readability doesn't have to be sacrificed, and this idiom would put pressure on creating that future. (Victor's other work on optimization is about both of those kinds of optimizers, I think. I suggested this optimization, but never did anything about it. It's on the todo list: https://fatoptimizer.readthedocs.io/en/latest/todo.html#random)
On 11.05.2016 23:57, Ethan Furman wrote:
On 05/11/2016 02:40 PM, Zachary Ware wrote:
On Wed, May 11, 2016 at 3:36 PM, Ian Kelly wrote:
You just weren't explicit enough:
--> sleep(timedelta(100).total_seconds())
This is drifting pretty far from the topic at hand, though.
There was a topic at hand?
Oh, yeah, keyword arguments!
Actually, it's very apropos: each of those examples would have been either obviously correct or obviously wrong if the parameter names had been used.
Indeed. It's something that's already baked in our internal style guidelines. Mostly because we are too lazy to read the docs or jump to definition. Sven PS: It's interesting to see how easy it is to get off-topic as soon as an example is present.
participants (22)
-
Bar Harel
-
Barry Warsaw
-
Chris Angelico
-
Chris Barker
-
Eric V. Smith
-
Ethan Furman
-
Franklin? Lee
-
Guido van Rossum
-
Ian Kelly
-
Joao S. O. Bueno
-
Joseph Jevnik
-
Joseph Martinot-Lagarde
-
Koos Zevenhoven
-
M.-A. Lemburg
-
Mark Dickinson
-
Nick Coghlan
-
Random832
-
Serhiy Storchaka
-
Steven D'Aprano
-
Sven R. Kunze
-
Terry Reedy
-
Zachary Ware