Рубрики
Без рубрики

Вы действительно думаете, что знаете строки в Python?

Узнайте о некоторых прекрасных концепциях строк в Python.

Автор оригинала: Satwik Kansal.

В прошлые выходные из любопытства я решил исследовать, как строки реализуются в Python. Результаты были ошеломляющими. Я понял, как мало я знаю о различных концепциях строк и оптимизациях в Python.

Что еще более интересно, так это то, что многие программисты не знают об этих концепциях. С помощью этих идей у меня есть возможность сбить с толку нескольких программистов на Python.

Хорошо, я начну с простого фрагмента кода, который я запустил в своем интерпретаторе Python,

>>> a = "wtf"
>>> b = "wtf"
>>> a is b
True

>>> a = "wtf!"
>>> b = "wtf!"
>>> a is b
False

>>> a, b = "wtf!", "wtf!"
>>> a is b
True

В этом есть смысл, верно?

Ну, а если нет, держись. Через некоторое время все будет казаться очевидным. Но прежде чем мы это сделаем, позвольте мне бросить вам мой следующий фрагмент.

>>> a = "some_string"
>>> id(a)
140420665652016
>>> id("some" + "_" + "string") # Notice that both the ids are same.
140420665652016

Ладно, одна последняя атака, прежде чем мы сделаем перерыв.

>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False

Ни один из этих результатов не имел бы смысла для меня пару недель назад, и, увидев их, я столкнулся бы с экзистенциальным кризисом как обычный программист Python.

Скриншот с 2018-01-11 23-25-43.png

В любом случае, если вы понятия не имеете, что здесь происходит, каждый результат-это просто следствие концепции, называемой здесь “Интернированием строк”.

Подождите, что это за “интернирование строк”?

Поскольку строки являются неизменяемыми объектами в Python. Несколько переменных могут ссылаться на один и тот же строковый объект для экономии памяти, а не создавать каждый раз новый объект.

Python иногда неявно интернирует строки. Решение о том, когда неявно интернировать строку, зависит от реализации. Однако некоторые факты могут помочь нам легко догадаться, будет ли строка интернирована или нет:

  • Все строки длины 0 и длины 1 интернируются.
  • Строки интернируются во время компиляции ( 'wtf' будет интернирован, но ".join(['w', 't', 'f'] не будет интернирован)
  • Строки, которые не состоят из букв ASCII, цифр или символов подчеркивания, не являются Интернетом. Это объясняет, почему 'wtf!' не был интернирован из-за ! .

О, я понимаю, но почему это было так “wtf! интернет в а,, “wtf!”

Ну а когда a и b установлены в "wtf!" в той же строке интерпретатор Python создает новый объект, а затем одновременно ссылается на вторую переменную. Если вы делаете это на отдельных строках, он не “знает”, что уже есть wtf! как объект (потому что "wtf!" не является имплицитно интернированным в соответствии с упомянутыми выше фактами).

Это оптимизация компилятора, и она особенно применима к интерактивной среде. Когда вы вводите две строки в живом интерпретаторе, они компилируются отдельно и, следовательно, оптимизируются независимо. Если бы вы попробовали этот пример в файле .py , вы бы не увидели такого же поведения, потому что файл компилируется все сразу.

**Ах, но что с этим 'a' * 21-это 'aaaaaaaaaaaaaaaaaaa' ?

Выражение 'a'*20 заменяется на 'aaaaaaaaaaaaaaaaaaa' во время компиляции, чтобы уменьшить количество тактов во время выполнения. Но поскольку байт-код python , сгенерированный после компиляции, хранится в файлах .pyc , строки длиной более 20 отбрасываются для оптимизации peephole (Почему? Представьте себе размер файла .pyc, сгенерированного в результате выражения ‘a’*10**10)

А теперь еще раз просмотрите эти фрагменты. Теперь все кажется очевидным, не так ли? В этом вся прелесть Питона! ️

Подождите, это еще не все!!!

Да, мир не придет легко. Вот еще один фрагмент:

>>> print("\\ some string \\")
>>> print(r"\ some string")
>>> print(r"\ some string \")

    File "", line 1
      print(r"\ some string \")
                             ^
SyntaxError: EOL while scanning string literal

Что там произошло?

На самом деле, в необработанном строковом литерале, как указано префиксом r , обратная косая черта не имеет особого значения. Однако на самом деле интерпретатор просто изменяет поведение обратных косых черт, чтобы они передавали себя и следующий символ. Вот почему обратные косые черты не работают в конце необработанной строки.

Вот еще один…

>>> print('wtfpython''')
wtfpython
>>> print("wtfpython""")
wtfpython
>>> # The following statements raise `SyntaxError`
>>> # print('''wtfpython')
>>> # print("""wtfpython")

Можете ли вы догадаться, почему print("'wtf python') или print ("""wtf python") вызовет здесь “Синтаксическую ошибку”?

Если ваш ответ отрицательный, пришло время ввести новую концепцию под названием “неявная конкатенация строк”.” Python поддерживает неявную конкатенацию строковых литералов|/.

Например:

  >>> print("wtf" "python")
  wtfpython
  >>> print("wtf" "") # or "wtf"""
  wtf

"' “‘ и “”” также являются строковыми разделителями в Python, что вызывает синтаксическую ошибку , поскольку интерпретатор Python ожидал завершающую тройную кавычку в качестве разделителя при сканировании встречающегося в данный момент строкового литерала в тройных кавычках.

Моар.. (это последнее, мизинчатое обещание)

# using "+", three strings:
>>> timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
0.25748300552368164
# using "+=", three strings:
>>> timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
0.012188911437988281

Обратите внимание на резкую разницу во времени выполнения s1 + s3 и s1 + s2 + s3 ? += на самом деле быстрее, чем + для объединения более двух строк, потому что первая строка (например, s1 для s1 + s3 ) не уничтожается при вычислении полной строки.

Обратите внимание на резкую разницу во времени выполнения || s1 + s3 || и || s1 + s2 + s3 || ? || += || на самом деле быстрее, чем || + || для объединения более двух строк, потому что первая строка (например, || s1 || для || s1 + s3 || ) не уничтожается при вычислении полной строки.

Прежде чем я закончу, вот быстрый факт, о, подождите, сначала быстрое осознание: “Строки-это набор символов, очень похожих на нас самих.” (Я знаю, что это уже слишком). Теперь, переходя к факту, 'a'[0][0][0][0][0] это семантически корректное утверждение в Python. Почему? Поскольку строки являются неизменяемыми последовательностями (итераблями, поддерживающими доступ к элементам с использованием целочисленных индексов) в Python. Реализация вышеперечисленных концепций и оптимизаций в Python возможна именно благодаря этому факту.

Ладно, на этом мы закончим. Надеюсь, вы найдете этот пост интересным и информативным. Если вы хотите узнать больше о таких скрытых драгоценных камнях Python, я бы рекомендовал вам проверить What the f**ck Python? который представляет собой кураторскую коллекцию таких тонких и хитрых фрагментов.