PEP 616: Fix behavior of code snippets (#1336)

This commit is contained in:
sweeneyde 2020-03-23 20:02:46 -04:00 committed by GitHub
parent 7a2c2168b6
commit dcb29f4019
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 30 additions and 28 deletions

View File

@ -52,7 +52,7 @@ following behavior::
self_str = str(self)
if isinstance(prefix, tuple):
for option in prefix:
for option in tuple(prefix):
if not isinstance(option, str):
raise TypeError()
option_str = str(option)
@ -79,12 +79,14 @@ following behavior::
self_str = str(self)
if isinstance(suffix, tuple):
for option in suffix:
for option in tuple(suffix):
if not isinstance(option, str):
raise TypeError()
option_str = str(option)
if option_str and self_str.endswith(option_str):
if not option_str:
return self_str[:]
if self_str.endswith(option_str):
return self_str[:-len(option_str)]
return self_str[:]
@ -98,23 +100,24 @@ following behavior::
else:
return self_str[:]
Note that without the check for the truthyness of suffixes,
``s.cutsuffix('')`` would be mishandled and always return the empty
Note that without the check for the truthyness of suffixes,
``s.cutsuffix('')`` would be mishandled and always return the empty
string due to the unintended evaluation of ``self[:-0]``.
Methods with the corresponding semantics will be added to the builtin
Methods with the corresponding semantics will be added to the builtin
``bytes`` and ``bytearray`` objects. If ``b`` is either a ``bytes``
or ``bytearray`` object, then ``b.cutsuffix()`` and ``b.cutprefix()``
will accept any bytes-like object or tuple of bytes-like objects as an
argument. The one-at-a-time checking of types matches the implementation
of ``startswith()`` and ``endswith()`` methods.
The ``self_str[:]`` copying behavior in the code ensures that the
The ``self_str[:]`` copying behavior in the code ensures that the
``bytearray`` methods do not return ``self``, but it does not preclude
the ``str`` and ``bytes`` methods from returning ``self``. Because
``str`` and ``bytes`` instances are immutable, the ``cutprefix()``
and ``cutsuffix()`` methods on these objects may (but are not
required to) make the optimization of returning ``self`` if
required to) make the optimization of returning ``self`` if
``type(self) is str`` (``type(self) is bytes`` respectively)
and the given affixes are not found, or are empty. As such, the
following behavior is considered a CPython implementation detail, and
@ -142,6 +145,7 @@ methods for control flow instead of testing the lengths as above.
The two methods will also be added to ``collections.UserString``, with
similar behavior.
Motivating examples from the Python standard library
====================================================
@ -149,17 +153,17 @@ The examples below demonstrate how the proposed methods can make code
one or more of the following:
1. Less fragile:
The code will not depend on the user to count the length of a literal.
2. More performant:
The code does not require a call to the Python built-in ``len``
function, nor to the more expensive ``str.replace()``
method.
3. More descriptive:
The methods give a higher-level API for code readability, as
opposed to the traditional method of string slicing.
@ -231,9 +235,6 @@ cookiejar.py
test_concurrent_futures.py
--------------------------
In the following example, the meaning of the code changes slightly,
but in context, it behaves the same.
- Current::
if name.endswith(('Mixin', 'Tests')):
@ -258,13 +259,14 @@ Expand the lstrip and rstrip APIs
---------------------------------
Because ``lstrip`` takes a string as its argument, it could be viewed
as taking an iterable of length-1 strings. The API could therefore be
generalized to accept any iterable of strings, which would be
successively removed as prefixes. While this behavior would be
consistent, it would not be obvious for users to have to call
``'foobar'.cutprefix(('foo,))`` for the common use case of a
as taking an iterable of length-1 strings. The API could therefore be
generalized to accept any iterable of strings, which would be
successively removed as prefixes. While this behavior would be
consistent, it would not be obvious for users to have to call
``'foobar'.lstrip(('foo',))`` for the common use case of a
single prefix.
Remove multiple copies of a prefix
----------------------------------
@ -272,20 +274,20 @@ This is the behavior that would be consistent with the aforementioned
expansion of the ``lstrip``/``rstrip`` API -- repeatedly applying the
function until the argument is unchanged. This behavior is attainable
from the proposed behavior via by the following::
>>> s = 'foobar' * 100 + 'bar'
>>> prefixes = ('bar', 'foo')
>>> s = 'FooBar' * 100 + 'Baz'
>>> prefixes = ('Bar', 'Foo')
>>> while len(s) != len(s := s.cutprefix(prefixes)): pass
>>> s
'bar'
'Baz'
or the more obvious and readable alternative::
>>> s = 'foo' * 100 + 'bar'
>>> prefixes = ('bar', 'foo')
>>> s = 'FooBar' * 100 + 'Baz'
>>> prefixes = ('Bar', 'Foo')
>>> while s.startswith(prefixes): s = s.cutprefix(prefixes)
>>> s
'bar'
'Baz'
Raising an exception when not found
@ -312,7 +314,7 @@ of ``cutprefix`` (the same arguments hold for ``cutsuffix``).
- ``lstrip(string=...)``
This would avoid adding a new method, but for different
This would avoid adding a new method, but for different
behavior, it's better to have two different methods than one
method with a keyword argument that select the behavior.
@ -363,7 +365,7 @@ References
(https://mail.python.org/archives/list/python-dev@python.org/thread/OJDKRIESKGTQFNLX6KZSGKU57UXNZYAN/#CYZUFFJ2Q5ZZKMJIQBZVZR4NSLK5ZPIH)
.. [7] [Python-Dev] "strip behavior provides inconsistent results with certain strings"
(https://mail.python.org/archives/list/python-dev@python.org/thread/ZWRGCGANHGVDPP44VQKRIYOYX7LNVDVG/#ZWRGCGANHGVDPP44VQKRIYOYX7LNVDVG)
.. [#confusion] Comment listing Bug Tracker and StackOverflow issues
.. [#confusion] Comment listing Bug Tracker and StackOverflow issues
(https://mail.python.org/archives/list/python-ideas@python.org/message/GRGAFIII3AX22K3N3KT7RB4DPBY3LPVG/)