Автор оригинала: Martijn Pieters.
Эксперт по Python Codementor и легенда переполнения стека Мартиджн Питерс присоединился к нам на часовом сеансе в офисе, чтобы дать нам краткое руководство по оптимизации Python.
Текст ниже представляет собой резюме, сделанное командой Codementor, и может отличаться от оригинального видео, и если вы видите какие-либо проблемы, пожалуйста, сообщите нам об этом!
Распространенная Ловушка, Которую Могут Сделать Начинающие Кодеры Python
Прежде всего, все в Python-это объект, который, в отличие от C или любого другого языка программирования, включает в себя числа. В частности, целые числа также являются объектами, и каждый раз, когда кодер Python использует целое число, Python должен создавать объект.
Однако, чтобы ускорить процесс и избавить кодеров от процесса постоянного создания чисел, следует отметить, что обычно используемые целые числа от -5 до 256 кэшируются в Python. Таким образом, каждый раз, когда программист Python запрашивает число, такое как 5, Python уже имеет объект и использует его каждый раз, когда вызывается число 5. Другими словами, целые числа от -5 до 256 являются синглетами, что означает, что они всегда являются одним и тем же объектом, и существует только одна копия каждого числа. Это возможно, потому что целые числа неизменяемы, так как никто не может изменить их значение.
Однако этот процесс использования кэшированных целых чисел также создает возможность для начинающих программистов Python запутаться в двух операторах сравнения в Python: is (идентичность) и == (равенство). Is проверяет две ссылки, чтобы проверить, указывают ли они на один и тот же объект, в то время как значение двух объектов может быть или не быть одним и тем же.
“Идентичность-это не то же самое, что равенство”
Одна из самых распространенных ошибок новичков Python заключается в том, что они путают is с равенством, и поскольку обычно используемые целые числа кэшируются, они могут не осознавать свою ошибку в течение длительного времени.
Ниже приведены два примера, иллюстрирующие эту проблему:
//Пример 1//
В этом примере foo выходит истинным, потому что они имеют одинаковое значение. Однако foo is bar также оказывается верным, потому что Python дает один и тот же точный объект 42 как для foo, так и для bar вместо создания другого. Таким образом, как показано в примере, если вы используете функцию id() для получения адреса памяти уникальных идентификаторов foo и bar, они оба указывают на один и тот же адрес. Начинающие программисты на Python могут не заметить, что они допустили ошибку, и ожидать, что – это то же самое, что равенство.
Тем не менее, проблема возникает, когда используются большие числа.//Пример 2//
Поскольку кэшируются только целые числа от -5 до 256, 10 * 1000 is 10000 оказывается ложным, так как Python создает новые объекты как для 10 * 1000, так и для 10 000. Это может быть довольно запутанным для некоторых новичков Python, поэтому они должны знать, что is означает идентичность и равенство.
Интернирование строк для повышения эффективности
Интерпретатор Python много работает со строками, и, как и небольшие целые числа, строки также могут быть повторно использованы Python с помощью немного другой техники, называемой интернированием. При создании новой строки интерпретатор Python может выбрать, следует ли хранить кэшированную копию этой строки. Это происходит при определенных обстоятельствах, в частности, для идентификаторов.
Поэтому, если строка начинается с буквы или символа подчеркивания и содержит только буквы, символы подчеркивания или цифры, Python интернирует строку и создаст для нее хэш. Поскольку большинство вещей в Python являются словарями, Python должен выполнять много поисков идентификаторов, и, используя строки идентификаторов, процесс поиска может быть значительно ускорен. Другими словами, идентификаторы хранятся в таблице, и Python создает хэш из строкового объекта для будущих поисков. Такая оптимизация происходит во время компиляции, и строковые литералы, которые выглядят как идентификаторы, также будут интернированы.
Программисты на Python могут использовать стажировку при использовании своей собственной строки для поиска слотов. Например, было бы полезно использовать интернирование при работе с большой программой обработки текста, которая требует много замен или поиска и отвечает на большое количество сетевых сообщений с сопоставлениями. Строки, считываемые из файла или сетевого подключения, не интернируются (хотя строковые литералы, возможно, были интернированы), но вы можете использовать функцию intern() для создания собственной интернированной копии таких строк.
Оптимизация глазка
Чтобы избавить вас от создания подобных объектов при каждой загрузке кода, Python хранит их в виде литералов.
//Пример 3//
В этом коде используются два литерала—строковый литерал и целочисленный литерал. Теперь существует объект кода, присвоенный “foo”, и связанная с ним константа. Таким образом, в приведенном выше примере none, hello world, space ( ” ) и число 20 хранятся как константы.
Помимо хранения констант, в Python есть еще несколько приемов оптимизации, таких как упрощение нескольких типов выражений и замена некоторых изменяемых объектов неизменяемыми объектами.
Выражения, которые упрощает Python, являются вычислениями в коде, поэтому программисты могут хранить функции как вычисления, так как оптимизатор глазка Python будет хранить результат выражения вместо повторного вычисления при каждом использовании функции. В принципе, все, что является неизменяемым и имеет выражение, предварительно вычисляется во время компиляции и сохраняется, включая любой тип последовательности, например строки. Если строка состоит из нескольких частей и собрана вместе с классом, она будет заменена конечным результатом. Однако все, что длиннее 20 символов, будет проигнорировано.
//Пример 4//
В приведенном выше примере “duration” и “neener” были расширены в результат, поэтому Python будет загружать 864 000 каждый раз, когда упоминается “duration”, и Python будет загружать neenerneenerneener для выражения “neener”. Тем не менее, кортеж big_constant не был расширен, потому что оптимизатор глазка умно относится к пространству. Кортеж из 3000 элементов сделает файл байт-кода необычайно большим, поэтому последовательность может быть умножена не более чем в 20 раз. Таким образом, оптимизация глазка имеет ограничения, так как строки часто превышают 20 символов. Тем не менее, оптимизация глазка по-прежнему весьма полезна для избавления программистов от необходимости беспокоиться обо всех константах, которые они могут использовать.
Кроме того, как уже упоминалось выше, оптимизация глазка также может заменить изменяемые объекты неизменяемыми. Изменяемый литерал набора может быть изменен в принципе, так как это действительно неизменяемый объект, такой как объект списка.
В примере на этом слайде тест in используется для проверки принадлежности литерала, и Python заменяет набор замороженным набором, который будет сохранен в коде. Таким образом, можно и нужно использовать наборы всякий раз, когда возникает необходимость в тестировании на членство, поскольку тест имеет постоянную стоимость. Другими словами, независимо от того, насколько велик набор, Python всегда будет занимать одинаковое количество времени для проверки членства, и это всегда будет быстрее, чем тестирование по кортежу или списку.
Кроме того, Python делает то же самое со списками. Если объект литерального списка помещается в тест членства, Python заменит его фактической константой кортежа в коде, как в примере ниже.
Чтобы увидеть, что происходит с оптимизацией кода и что делает Python, можно использовать инструмент разборки. Модуль возвращает исходный байт-код или фрагмент кода, который можно передать в функцию, чтобы увидеть, как выглядит объект кода.
Важно отметить, что работают только литералы. Если набор или список не является литералом, оптимизация не выполняется. Однако это не относится к спискам, таким как кортежи, поскольку кортеж может быть создан с помощью буквального синтаксиса со скобками и, следовательно, может храниться как один постоянный объект.