Как работает 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_startswithendswith) проверьте для кортежа аргумент, а затем для строки. Затем они вызывают tailmatch в соответствующем направлении. Это, возможно, очень легко понять в его нынешнем состоянии, даже для незнакомых с C-кодом.

Добавление других случаев приведет только к более раздутому и сложному коду для небольшой выгоды, а также потребует аналогичных изменений для любых других частей объекта unicode.

Ответ 3

На аналогичной ноте, вот фрагмент последние изменения подписи str.startswith. Хотя он вкратце упомянул об этом факте, что str.startswith принимает строку или кортеж строк и не излагает, разговор информативен о решениях и причинах боли, которые основные разработчики и участники рассказали о переходе к настоящему API.