Как работает str.startswith?
Я немного играл с startswith()
, и я обнаружил что-то интересное:
>>> tup = ('1', '2', '3')
>>> lis = ['1', '2', '3', '4']
>>> '1'.startswith(tup)
True
>>> '1'.startswith(lis)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: startswith first arg must be str or a tuple of str, not list
Теперь ошибка очевидна, и листинг списка в кортеж будет работать отлично, как это было вначале:
>>> '1'.startswith(tuple(lis))
True
Теперь, мой вопрос: почему первым аргументом должна быть str или кортеж префиксов str, но не список префиксов str?
AFAIK, код Python для startswith()
может выглядеть следующим образом:
def startswith(src, prefix):
return src[:len(prefix)] == prefix
Но это меня просто смущает больше, потому что даже с учетом этого все равно не должно быть никакого значения, является ли список или кортеж. Что мне не хватает?
Ответы
Ответ 1
Технически нет оснований принимать другие типы последовательностей, нет. Исходный код примерно делает это:
if isinstance(prefix, tuple):
for substring in prefix:
if not isinstance(substring, str):
raise TypeError(...)
return tailmatch(...)
elif not isinstance(prefix, str):
raise TypeError(...)
return tailmatch(...)
(где tailmatch(...)
выполняется фактическое сопоставление).
Таким образом, любой цикл будет выполняться для этого цикла for
. Но все другие API-интерфейсы для тестирования строк (а также isinstance()
и issubclass()
), которые принимают несколько значений, также принимают только кортежи, и это говорит вам как пользователю API, что можно с уверенностью предположить, t быть мутированным. Вы не можете мутировать кортеж, но метод может теоретически изменить список.
Также обратите внимание, что вы обычно проверяете фиксированное количество префиксов или суффиксов или классов (в случае isinstance()
и issubclass()
); реализация не подходит для большого количества элементов. Кортеж подразумевает, что у вас ограниченное количество элементов, а списки могут быть сколь угодно большими.
Далее, если любой итерабельный или тип последовательности будет приемлемым, тогда это будет включать строки; одна строка также является последовательностью. Должен ли тогда один строковый аргумент обрабатываться как отдельные символы или как один префикс?
Иными словами, это ограничение самодокументации, что последовательность не будет мутирована, согласуется с другими API-интерфейсами, она подразумевает ограниченное количество элементов для тестирования и устраняет двусмысленность относительно того, как необходимо обработать один строковый аргумент.
Обратите внимание, что это было описано ранее в списке Python Ideas; см. этот поток; Главный аргумент Guido van Rossum заключается в том, что вы либо особый случай для одиночных строк, либо только для приема кортежа. Он выбрал последнее и не видит необходимости изменять это.
Ответ 2
Это уже было предложено на Python-идеях пару лет назад: str.startswith
принимать любой итератор вместо одного кортежа и GvR это сказать:
Текущее поведение является преднамеренным, а двусмысленность строк сами по себе являются итерабельными. Так как startswith()
почти всегда называемый буквальным или кортежем литералов, я вижу мало нужно расширять семантику.
В дополнение к этому, по-видимому, не было реальной мотивации к тому, почему это нужно делать.
Текущий подход сохраняет все просто и быстро,
unicode_startswith
(и endswith
) проверьте для кортежа аргумент, а затем для строки. Затем они вызывают tailmatch
в соответствующем направлении. Это, возможно, очень легко понять в его нынешнем состоянии, даже для незнакомых с C-кодом.
Добавление других случаев приведет только к более раздутому и сложному коду для небольшой выгоды, а также потребует аналогичных изменений для любых других частей объекта unicode.
Ответ 3
На аналогичной ноте, вот фрагмент последние изменения подписи str.startswith
. Хотя он вкратце упомянул об этом факте, что str.startswith
принимает строку или кортеж строк и не излагает, разговор информативен о решениях и причинах боли, которые основные разработчики и участники рассказали о переходе к настоящему API.