9 странностей Python для C++ программистов

Python можно назвать странным языком программирования.

По крайней мере, когда вы переходите с других языков вроде C, C++, C#, Java, PHP или JavaScript. Все эти языки следуют типичному синтаксису и разделяют множество принципов действия. Можно, конечно, также обнаружить большинство из этих принципов и в Python, но при этом он рушит закономерности многих из них. Иногда это ведет к лучшему, а иногда к худшему. Среди топ-5 языков программирования по рейтингу TIOBE Python единственный не использует синтаксис фигурных скобок в стиле C.

Я работал над превращением Raspberry Pi в веб-камеру, в ходе чего мне пришлось реализовать сервер на Python, потому что лучший API для взаимодействия с камерой Pi написан именно на нем. До этого я уже использовал Python несколько раз, но это был самый крупный проект, и вскоре я не только вспомнил некоторые из его странностей, но и обнаружил новые.

1. Пробелы

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

К счастью, подобные ошибки легко обнаружить, и на их исправление уходит всего пара секунд, но все же они каждый раз меня раздражают. Проблем поубавилось, когда я перешел на Visual Studio Code, который неплохо избегает большинства ошибок с пробелами, но я все еще получаю по одной-две в день, особенно при копировании какого-либо кода, где вместо пробелов использовалась табуляция.

Тем не менее есть и другие типы ошибок, которые найти уже куда сложнее. Когда перед вами сложный раздел кода со множеством слоев вложенных циклов и инструкций if, и при этом вы не особо стараетесь над отступами, то можно легко сделать один ошибочно. В отличие от проблемы “пробелы vs. табуляция” это уже не синтаксическая ошибка, и сообщение вы не получите. Очевидно, что можно также ошибочно расположить строку кода и в С++, но для этого придется вынести ее вне фигурных скобок, охватывающих область, что по ошибке происходит не так уж часто.

2. Синтаксис

При том, что Python является единственным ведущим языком, где пробелы играют столь важную роль, он также единственный из вышеназванного рейтинга топ-5, в котором не используется синтаксис фигурных скобок в стиле С. Так как С стал одним из первых широко распространившихся языков программирования, многие разработчики привыкли именно к его синтаксису, в связи с чем он был позаимствован и рядом других более современных языков.

Для меня синтаксис фигурных скобок из С/С++ выглядит и воспринимается более структурированным в сравнении со структурой Python, основанной на пробелах. Но это, скорее всего, зависит от того, какой был ваш первый язык программирования.

Для тех, кто начинал с Python, фигурные скобки могут выглядеть странными и сбивать с толку. Если же вы начинали свой путь программиста с С/С++, то для вас наверняка было намного сложнее осваивать Python, чем другие языки вроде Java, PHP или JavaScript, в которых применяется схожая с С синтаксическая структура.

И даже несмотря на то, что я привык к синтаксису Python, для меня все равно гораздо сложнее переключаться с С++ на Python, чем, к примеру, на PHP.

3. Переменные класса

Вот очень простой фрагмент С++ кода, в котором есть класс с переменной члена байтового массива. Мы создаем два объекта, и каждый из них присваивает байтовому массиву уникальное значение. Затем мы выводим эти значения, и они отражаются в ожидаемом виде.

class TestClass
{
public:
    bytearray data = bytearray(1);
};

TestClass a = TestClass();
a.data[0] = 1;
TestClass b = TestClass();
b.data[0] = 2;
std::cout << (int)a.data[0] << std::endl;
std::cout << (int)b.data[0] << std::endl;

// Вывод:
// 1
// 2

А вот тот же фрагмент кода в Python:

class TestClass:
    data = bytearray(1)

a = TestClass()
a.data[0] = 1
b = TestClass()
b.data[0] = 2
print( a.data[0] )
print( b.data[0] )
print( TestClass.data[0] )

# Вывод:
# 2
# 2
# 2

Выглядит практически идентично, за исключением некоторых синтаксических отличий между C++ и Python. Однако при выводе все значения байтового массива оказываются одинаковыми, что довольно странно и неожиданно.

У меня был очень схожий код в недавнем проекте на Python, и поскольку он выглядел сильно похожим на то, к чему я привык в C++, то я и не раздумывал о нем дважды. Тем не менее моя программа не работала, и потребовалось немало времени на то, чтобы выявить проблему. Когда вы при работе в Python объявляете в классе переменную, то она по умолчанию становится переменной класса так же, как и статические переменные в C++.

Однако в этом процессе есть и другие подвохи. Взгляните сюда:

class IntTestClass:
    number = 0

a = IntTestClass()
a.number = 1
b = IntTestClass()
b.number = 2
print( a.number )
print( b.number )
print( IntTestClass.number )

# Вывод:
# 1 
# 2
# 0

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

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

4. Отсутствие прозрачности указателей и ссылок

К пункту выше прибавляется еще и недостаток в Python прозрачности при работе со ссылками и указателями. Некоторые значения передаются по ссылке, но просто глядя в код, вы никак не поймете, какие именно. Многие разработчики недолюбливают указатели и ссылки в C++, и если этот язык становится вашим первым, то я соглашусь, что они добавляют сложности. Тем не менее, как только у вас сформируется базовое понимание принципа их действия, они начинают привносить в код ясность, делая очевидным все происходящее.

5. Закрытые члены классов

Python также недостает возможности управления доступом для переменных классов и функций  —  все всегда открыто. Это не большая проблема, но я все же предпочитаю использовать закрытые функции и переменные для лучшего инкапсулирования и управления доступом. Так код становится намного чище и безопаснее.

Моя программа Raspberry Pi Webcam. Сервер написан на Python, а клиент на C++

6. Self vs. this

Еще одно небольшое отличие, к которому нужно привыкать при переходе с Python на C++состоит в использовании self вместо this. Self и this относятся к текущему экземпляру объекта, но по неясной причине Python является единственным ведущим языком, который для этой цели использует не this, а self. Кроме того, вам необходимо передавать ссылку self в функции Python явно, в то время как this всегда доступно в С++ неявно. 

7. Возвращение нескольких значений

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

8. Отсутствие строгой типизации

В отличие от С++, в Python не используются строгие типы данных, то есть вам не нужно объявлять тип переменной при ее создании. В Python это будет просто number = 5, а в C++ потребуется указать тип вроде int или float, например int number = 5;.

Я предпочитаю строго типизированные языки, потому что в них проще избегать определенных видов ошибок. Но при этом мне нравятся и многие слаботипизированные представители вроде PHP или JavaScript. Тем не менее в отношении Python это также нужно учитывать. В одной из моих последний программ, где требовалось отправлять двоичные данные по сети, разница между неявными типами данных Python и строгими типами в С++ была препятствием (к примеру, все числа с плавающей запятой в Python в С++ равнозначны double, а не float).

9. Отсутствие констант

В Python отсутствует встроенный способ объявления констант, что может несколько раздражать. Используя соглашение именовать константы заглавными буквами, можно указывать другим, что переменная должна быть постоянной, но это никак не остановит кого-либо от ее переназначения. При этом в С++ объявление переменных как const работает гораздо интереснее, делая код более чистым и безопасным.

Переключение между разными языками программирования всегда требует времени на привыкание, так как некоторые отличия неизбежно присутствуют. Однако Python делает многие вещи совершенно по-другому, в связи с чем переход на него с языка подобного С++ становится особенно сложным.

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

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

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Christian Behler: 9 Reasons Why Python Is Weird For C++ Developers

Предыдущая статьяВведение в потоки Redis
Следующая статьяРеальный способ стать хорошим программистом