Skip to content

bpo-44544: [doc] list all textwrap func kwargs#26999

Merged
ambv merged 12 commits intopython:mainfrom
jdevries3133:bpo-44544-textwrap-docs
Jul 28, 2021
Merged

bpo-44544: [doc] list all textwrap func kwargs#26999
ambv merged 12 commits intopython:mainfrom
jdevries3133:bpo-44544-textwrap-docs

Conversation

@jdevries3133
Copy link
Copy Markdown
Contributor

@jdevries3133 jdevries3133 commented Jul 2, 2021

  • list all kwargs for textwrap.wrap, textwrap.fill, and
    textwrap.shorten
  • now, there are nav links to attributes of TextWrap, which makes navigation
    much easier while minimizing duplication
  • revised descriptions of those functions accordingly
  • did not change actual function signatures; maybe we should?
    • if so, we should continue accepting additional kwargs as to not break
      existing code.
    • now, function signatures do not match the documentation
    • however, I noticed that while using pyright, it still shows all the kwargs
      through introspection. Maybe the mismatch is acceptable?

    https://bugs.python.org/issue44544

* list all kwargs for `textwrap.wrap`, `textwrap.fill`, and
  `textwrap.shorten`
* now, there are nav links to attributes of TextWrap, which makes navigation
  much easier while minimizing duplication
Copy link
Copy Markdown
Contributor

@akulakov akulakov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Left a few comments, nothing is wrong per se, just making it more consize..

@jdevries3133
Copy link
Copy Markdown
Contributor Author

@akulakov Thank you for your review!

@akulakov
Copy link
Copy Markdown
Contributor

akulakov commented Jul 2, 2021 via email

fix_sentence_endings=False, \
break_long_words=True, drop_whitespace=True, \
break_on_hyphens=True, tabsize=8, \
*, max_lines=None, **kwargs)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is placeholder? What does kwargs include?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that placeholder only has an effect in :func:shorten?

**kwargs is there just because the actual function signature is like this:

def wrap(text, width=70, **kwargs): ...

Any keyword argument will be accepted, but only correctly spelled kwargs will have an effect. I added an additional note to more specifically highlight this behavior; it adds on to the paragraphs where these kwargs are listed:

Be careful not to misspell keyword arguments. Misspelled keyword arguments will silently have no effect, and an
exception will not be raised.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's the case, the kwargs are passed to TextWrapper, which will not accept unknown keyword args:

/usr/local/Cellar/python@3.9/3.9.1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/textwrap.py in shorten(text, width, **kwargs)
    403         'Hello [...]'
    404     """
--> 405     w = TextWrapper(width=width, max_lines=1, **kwargs)
    406     return w.fill(' '.join(text.strip().split()))
    407

TypeError: __init__() got an unexpected keyword argument 'zz'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

placeholder can apply to all 3 functions because it depends on max_lines -- if max_lines is specified and results in removing of some lines, placeholder will be added per docs.

@jdevries3133
Copy link
Copy Markdown
Contributor Author

If anything, I'd say maybe change the function signature back to what it originally (.. function:: wrap(text, width=70, **kwargs)), and keep the links. With that many arguments, the function signature kind of looks like spaghetti and I think that the browsing experience is much stronger with the links.

@akulakov
Copy link
Copy Markdown
Contributor

akulakov commented Jul 2, 2021 via email

@akulakov
Copy link
Copy Markdown
Contributor

akulakov commented Jul 3, 2021

A couple of thoughts:

I haven't seen instances in Python docs where a list of the same links would be duplicated multiple times closely, so I feel like that doesn't match this pattern of avoiding too much repetition.

An option that "splits the difference" would be to have the list of links for the first function and omit lists for the other two. I think either this option or not having links at all would work well.

@akulakov
Copy link
Copy Markdown
Contributor

akulakov commented Jul 7, 2021

I saw a very similar example in current docs: https://docs.python.org/3.11/library/logging.html#logging.LoggerAdapter -- the methods are from a different class but are linked here, in one paragraph rather than separate lines. Seems like a good approach, WDYT?

@jdevries3133
Copy link
Copy Markdown
Contributor Author

@akulakov that seems like a good precedent to follow. See 1306f2d; what do you think?

@akulakov
Copy link
Copy Markdown
Contributor

akulakov commented Jul 7, 2021

@akulakov that seems like a good precedent to follow. See 1306f2d; what do you think?

Looks good, thanks!

@merwok
Copy link
Copy Markdown
Member

merwok commented Jul 9, 2021

The few lines with inline links in the logging example look better than the list of raw names in the screenshot here, though.

@akulakov
Copy link
Copy Markdown
Contributor

akulakov commented Jul 9, 2021

@merwok yep, the screenshot is outdated. We went with the list of inline links.

@jdevries3133 btw Serhiy also left a few requests for change above (not sure if you saw them).

:attr:`.drop_whitespace`, :attr:`.break_on_hyphens` :attr:`.tabsize`, and
:attr:`.max_lines`. Be careful not to misspell keyword arguments.
Misspelled keyword arguments will silently have no effect, and an exception
will not be raised.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my view repetition in the docs is not better than repetition in code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The root cause of the repetition is that these functions all recieve a laundry list of keyword arguments, and they are all the same or highly similar. In that sense, the API is repetitive. Maybe it could have been designed better if we had dataclasses for grouping the values passed in or something like that, but I think the repetition in the documentation is a consequence of the library'a design, and we aren't going to change the API design.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the code, this list of args appears once in the constructor of TextWrapper. The module-level wrapper functions just forward their kwargs to this constructor. It would be enough to define all these args once and in other places say "the meaning of the parameters are the same as over there".

It's easy for these repetitions to go out of sync with each other (if you add an arg to TextWrapper.init you need to update the doc in multiple places).

I don't see what the benefit of the repetition is. For a user trying to study the library for the first time it's just tedious. For someone looking up a single function, clicking on a link to the TextWrapper constructor is not complicated.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My preference would also be to avoid repeated list of links. I previously suggested inline links as a compromise between no links and the "tall" list with a link per line as in initial revision. I don't have a strong opinion though, as long as it's not a "tall" list.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iritkatriel I'm sorry, I thought you were talking about repetition between the function signature and the inline links but now I realize I don't think that's what you're talking about. I'm going to take a closer look later today, sorry for any confusion.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry I was vague.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iritkatriel I see what you are saying. I went ahead and removed the repetitive links all together. Do you think that the explicit function signatures is a good change? Otherwise, there's nothing to be done here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, I wonder if the documentation working group can come up with suggestions. This is not the only library that has this pattern. @JulienPalard @Mariatta

arguments. Note that the whitespace is collapsed before the text is passed
to the :class:`TextWrapper` :meth:`fill` function, so changing the value of
:attr:`.tabsize`, :attr:`.expand_tabs`, :attr:`.drop_whitespace`, and
:attr:`.replace_whitespace` will have no effect. Be careful not to misspell
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last sentence can be omitted because the same can be said for any func / method in stdlib.

@akulakov
Copy link
Copy Markdown
Contributor

One interesting question that I've just thought about is whether **kwargs should be included in the sigs of 3 functions. The sigs don't follow the code exactly, that's the point of this PR. The users are meant to follow the signature and use the args listed there. **kwargs is not to be used by them.

So the argument is that having **kwargs in the sig will probably only add confusion; the only utility may conceivably be that user is alerted to the idea that if they accidentally use an arg from a different function (out of these 3), it will not raise a TypeError.

So it seems **kwargs is just a remnant from the old, intentionally empty sig. Thoughts?

Wraps the single paragraph in *text* (a string) so every line is at most
*width* characters long. Returns a list of output lines, without final
newlines.
newlines. *width* defaults to ``70``.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why duplicate info that is already visible in the signature?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just carried over from the current doc, and not relevant to the bpo, so I left it:

https://docs.python.org/3/library/textwrap.html#textwrap.wrap

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, my bad, in the old documentation, that sentence was in the docs for textwrap.wrap, and for some reason I though it'd be a good idea to bring it down here so that they match, but that's definitely not right! If anything, it'd be better to remove the unnecessary repetition from textwrap.wrap, but again, that's out of the scope of this bpo, and we can make a new little bpo for that problem after this one lands.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way, I'm working on reverting all changes to things other than the function signatures right now, because I think that's all we really want to do here.

@merwok
Copy link
Copy Markdown
Member

merwok commented Jul 28, 2021

The users are meant to follow the signature and use the args listed there. **kwargs is not to be used by them.

I don’t really understand what you mean here, maybe because **kwargs means two different things in function definitions and function calls.
The textwrap functions are defined with varkwargs, but not any param is accepted, only ones valid for TextWrap.
As for calling, users can certainly use kwargs unpacking, if they have a dict of valid params on hand.

@akulakov
Copy link
Copy Markdown
Contributor

The users are meant to follow the signature and use the args listed there. **kwargs is not to be used by them.

I don’t really understand what you mean here, maybe because **kwargs means two different things in function definitions and function calls.
The textwrap functions are defined with varkwargs, but not any param is accepted, only ones valid for TextWrap.
As for calling, users can certainly use kwargs unpacking, if they have a dict of valid params on hand.

I meant to say that in this particular case, **kwargs would imply to a user that they can use some other kwargs in addition to ones explicitly listed. But if they did that, they would either get a TypeError from TextWrapper, or in case of one of the functions, the kwargs they might use would be a no-op, and so should not be used.

@jdevries3133
Copy link
Copy Markdown
Contributor Author

jdevries3133 commented Jul 28, 2021

@akulakov @merwok @serhiy-storchaka @iritkatriel I went ahead and reverted any changes other than those to the documented function signature. I think that changes to the function signature are the closest to the bpo, and make the most sense to add. I think that Irit made good points about the redundant links introducing a maintainability problem and not providing a significant enough usability improvement.

Andrei, I agree that documenting **kwargs is not necessary. The kwargs are always forwarded immediately to the TextWrap constructor, which does not accept arbitrary arguments:

def __init__(self,
             width=70,
             initial_indent="",
             subsequent_indent="",
             expand_tabs=True,
             replace_whitespace=True,
             fix_sentence_endings=False,
             break_long_words=True,
             drop_whitespace=True,
             break_on_hyphens=True,
             tabsize=8,
             *,
             max_lines=None,
             placeholder=' [...]'):

So, you get what you would expect from a misspelled keyword argument:

>>> import textwrap
>>> textwrap.wrap('hi', widt=10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/johndevries/.pyenv/versions/3.9.6/lib/python3.9/textwrap.py", line
378, in wrap
    w = TextWrapper(width=width, **kwargs)
TypeError: __init__() got an unexpected keyword argument 'widt'

I had this wrong in earlier revisions of this PR, but I (mostly) fixed it in 2927e0d; "fix: incorrect information; TextWrapper.init will raise Exc," and later removed all non-function-signature changes as I mentioned above.

Summary

@ambv ambv merged commit c1e39d6 into python:main Jul 28, 2021
@miss-islington
Copy link
Copy Markdown
Contributor

Thanks @jdevries3133 for the PR, and @ambv for merging it 🌮🎉.. I'm working now to backport this PR to: 3.10, 3.9.
🐍🍒⛏🤖

@bedevere-bot
Copy link
Copy Markdown

GH-27424 is a backport of this pull request to the 3.10 branch.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jul 28, 2021
(cherry picked from commit c1e39d6)

Co-authored-by: Jack DeVries <58614260+jdevries3133@users.noreply.github.com>
@bedevere-bot
Copy link
Copy Markdown

GH-27425 is a backport of this pull request to the 3.9 branch.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jul 28, 2021
(cherry picked from commit c1e39d6)

Co-authored-by: Jack DeVries <58614260+jdevries3133@users.noreply.github.com>
ambv pushed a commit that referenced this pull request Jul 28, 2021
(cherry picked from commit c1e39d6)

Co-authored-by: Jack DeVries <58614260+jdevries3133@users.noreply.github.com>
ambv pushed a commit that referenced this pull request Jul 28, 2021
(cherry picked from commit c1e39d6)

Co-authored-by: Jack DeVries <58614260+jdevries3133@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation in the Doc dir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants