enru

Руководство по использованию транспайлера Python → 11l → C++



Здесь [в данном руководстве] рассматриваются особенности работы транспайлера Python → 11l → C++, которые необходимо учитывать при написании Python-кода, чтобы он корректно скомпилировался данным транспайлером.

Целые числа


Как известно, в языке программирования Python используются целые числа произвольной точности. С одной стороны, это позволяет программисту не задумываться о том, насколько большое число может оказаться в переменной целого типа. Но с другой стороны работа с такими числами гораздо более ресурсоёмкая и менее эффективная. Поэтому все целые числа транспайлер Python → 11l → C++ рассматривает (как и принято в C++ по умолчанию) как 32-разрядные целые. Если требуется работать с числами большей разрядности (например, 64), то можно либо явно указать тип переменной как
Int64
, либо использовать опцию транспайлера
--int64
в командной строке (в этом случае все целые числа будут рассматриваться не как 32-, а как 64-разрядные). Явное указание типа переменной выглядит так:
s : Int64 = 0
Но в Python-е нет встроенного типа
Int64
. Поэтому, если хочется, чтобы код работал в Python, необходимо в начале программы один раз добавить такую строчку:
Int64 = int
Но транспайлер Python → 11l → C++ поймёт исходный Python-код и без такой строчки (а если встретит такую строку, то просто проигнорирует её).

Если требуется целое число произвольной точности, то следует использовать тип
BigInt
.

Символьные переменные


Аналогично целым 64-разрядным числам, транспайлер Python → 11l → C++ предоставляет возможность объявлять символьные переменные, то есть переменные, значением которых является код одного символа. Тип символьной переменной —
Char
. И аналогично
Int64
, чтобы получить код, который работает в Python, следует добавить такую строчку:
Char = str
Объявить массив из символов можно так:
charr : List[Char] = []
Это может использоваться как для увеличения производительности, так и для уменьшения занимаемой оперативной памяти (особенно если необходим очень большой массив символов, например).

Создание пустого списка/массива, словаря и множества


В отличие от языка Python, контейнеры (например массивы и множества) в языках 11l и C++ могут содержать элементы только одного типа (за исключением кортежей, которые могут содержать элементы различных типов), причем известного во время компиляции. В настоящий момент транспайлер Python → 11l → C++ не пытается определить тип контейнеров по их использованию, как это сделано в некоторых языках программирования (например Nemerle), поэтому при создании пустого контейнера необходимо указать тип его элементов явно.
Не поддерживаетсяПоддерживается
l = []
l : List[int] = []

или
l = [0] * 0
d = {}
d : Dict[str, int] = {}
s = set()
s = set() # int
dd = collections.defaultdict(int)
dd = collections.defaultdict(int) # str
Если типом значения словаря
collections.defaultdict
является список, то необходимо использовать следующую форму записи:
dd : DefaultDict[str, List[int]] = collections.defaultdict(list)

Создание непустого списка/массива, словаря и множества


Если контейнер инициализируется элементами, то указывать его тип не требуется:
l = [1, 2, 3]
d = {'a': 1, 'b': 2}
s = {1, 2, 3}
Так как все элементы списков должны быть одного типа, то такой список [взятый отсюда] не скомпилируется:
x = [0, 1, 2, 2, 2, 2, 1, 9, 3.5, 5, 8, 4, 7, 0, 6]
Чтобы это исправить достаточно указать тип первого элемента:
x = [float(0), 1, 2, 2, 2, 2, 1, 9, 3.5, 5, 8, 4, 7, 0, 6]

Передача списка как аргумента функции


В Python при передаче переменной типа list в функцию, её можно изменять внутри функции. И чтобы возможность изменять переменную сохранилась, транспайлеру Python → 11l → C++ необходимо явно указать тип этого аргумента.

Так, при объявлении функции
def decompress(compressed)
в коде-решении задачи LZW compression следует указать тип аргумента:
def decompress(compressed : List[int]):
    ...
Или так:
def decompress(compressed : list):
    ...

Переменные-члены классов


Чтобы код объявления нового класса успешно скомпилировался транспайлером Python → 11l → C++ необходимо указать типы всех переменных-членов этого класса.
Не скомпилируетсяСкомпилируется
class Error(Exception):
    def __init__(self, message, pos):
        self.message = message
        self.pos = pos


class Error(Exception):
    message : str
    pos     : int
    def __init__(self, message, pos):
        self.message = message
        self.pos = pos

Конкатенация строк


Так как язык программирования 11l не допускает использование оператора
+
для конкатенации строк (по причинам, обозначенным в документации языка), а использует свой синтаксис для данной операции, то транспайлер Python → 11l пытается угадать в каких случаях оператор
+
является арифметическим, а в каких является оператором конкатенации строк. В случае если ему не удалось правильно определить конкатенацию строк, следует между операндами добавить прибавление пустой строки (вместо
str1 + str2
следует писать
str1 + '' + str2
), например:
aa = ['1', '2']
bb = ['x', 'y']
for a in aa:
    for b in bb:
        print(a + '' + b)

Однако на практике в большинстве случаев, когда транспайлеру Python → 11l не удалось определить конкатенацию строк, достаточно добавить аннотацию типа. Например, в таком коде:
def rotated(s):
    return s[1:] + s[0]
достаточно указать тип аргумента
s
:
def rotated(s : str):
    return s[1:] + s[0]
(Вместо того, чтобы писать
s[1:] + '' + s[0]
.)
[Возможность использовать оператор
+
для конкатенации строк в 11l-коде может в будущих версиях транспайлера зависеть от опции
--max-compat-with-python
.]


Строковые и символьные литералы


Так как тип выражения
"A"
в языке 11l зависит от контекста (он может быть либо
String
, либо
Char
), в случае неверного определения типа строкового литерала транспайлером необходимо указать тип явно, т.е. писать
str('A')
либо
Char('A')
. Так, следующий Python-код не скомпилируется:
print(['AF'] + ['A']*5)
И необходимо писать так:
print(['AF'] + [str('A')]*5)

Также, если есть переменная типа
Char
(
ch : Char
), то чтобы присвоить ей символ необходимо написать
ch = Char('*')
вместо
ch = '*'
.

Множественная инициализация


На данный момент транспайлер Python → 11l → C++ не поддерживает множественную инициализацию, и вместо
a = b = 0
следует писать:
a = 0
b = 0
(Однако множественное присваивание (например,
a[0] = a[1] = 1
) поддерживается. Также поддерживаются конструкции
if a == b == c ...
и
if a < b < c ...
.)

Конструкция from ... import ...


На данный момент транспайлер Python → 11l → C++ не поддерживает данную конструкцию, и вместо такого кода:
from math import sqrt
print(sqrt(2))
следует писать:
import math
print(math.sqrt(2))

Однако в некоторых случаях необходимо использовать именно
from ... import ...
:
from functools import reduce
from functools import cmp_to_key
from itertools import product
from enum import IntEnum
from copy import copy, deepcopy
from typing import List, Tuple, NamedTuple, Dict, DefaultDict, Callable, Set, Optional, IO, TextIO, BinaryIO...
from _11l import *

Рекурсивный вызов функции


Если функция вызывает сама себя [рекурсивно], то при её объявлении необходимо явно указать тип возвращаемого значения (так как он не может быть выведен автоматически компилятором C++ в этом случае).
def find(x, y) -> None: # `-> None` здесь обязательно
    ...
    find(nx, ny) # из-за этого вызова

В случае рекурсивного вызова локальной функции требуется указывать не только тип возвращаемого значения, но и типы всех аргументов функции:
def fib(n):
    def f(n : int) -> int: # написать просто `def f(n):`
                           # или `def f(n) -> int:` нельзя
        if n < 2:
            return n
        return f(n-1) + f(n-2)
    return f(n)
Это связано с особенностью работы компилятора C++.

Обход элементов словаря в цикле for


При обходе словаря в цикле
for
в том случае, когда транспайлер Python → 11l не смог определить, что итерируемый контейнер — это словарь, следует указать явно, что необходимо обходить словарь по ключам, а не по парам (ключ, значение):
for k in d: # если не удалось определить тип `d`,
    print(k)               # то не скомпилируется

# И в таком случае необходимо писать так:
for k in d.keys():
    print(k)

Деление


Если делитель и делимое не являются числовыми литералами и при этом являются целочисленными, то деление выполняется по правилам C++ и Python 2, а не Python 3, т.е. деление в таком случае целочисленное. Чтобы получить вещественное деление необходимо использовать приведение к вещественному типу (т.е. вместо
a/b
написать
float(a)/b
или
a/float(b)
), либо использовать опцию транспайлера
--python-division
, чтобы операция
/
всегда выполнялась по правилам Python 3.

Остаток от деления


Если
a
может быть меньше
0
, то вместо
a % b
следует писать:
((a % b) + b) % b
или
r = a % b; if r < 0: r += b
Так как операция
%
в 11l выполняется [из соображений производительности] по правилам C++, а не Python.
Либо можно использовать опцию транспайлера
--python-remainder
, чтобы операция
%
выполнялась по правилам Python.

Конструкция yield


На данный момент транспайлер Python → 11l → C++ не поддерживает данную конструкцию, и вместо такого кода:
def squares(n):
    for i in range(n):
        yield i ** 2
следует писать:
def squares(n):
    r : List[int] = []
    for i in range(n):
        r.append(i ** 2)
    return r

Порядок вычисления аргументов функции


В отличие от языка Python в языке C++, к сожалению, порядок вычисления аргументов при передаче в функцию не определён, поэтому необходимо следить за возможной неправильной работой кода, производящего модификацию общих переменных во время передачи параметров функции.
Вот строчка кода из примера реализации калькулятора обратной польской записи на языке Python:
a.append(b[c](a.pop(),a.pop()))
Чтобы она корректно работала с транспайлером Python → 11l → C++, её необходимо переписать например так:
t = a.pop()
a.append(b[c](t, a.pop()))

Передача функции в качестве аргумента другой функции


Вот код из решения задачи Sort using a custom comparator:
def mykey(x):
    return -len(x), x.upper()
 
print(sorted(strings, key=mykey))
Он не скомпилируется транспайлером Python → 11l → C++, так как в сгенерированном C++ коде
mykey
— это шаблонная функция.
Следует либо указать типы аргументов функции:
def mykey(x : str):
    return -len(x), x.upper()
Либо использовать лямбда-функцию:
mykey = lambda x: (-len(x), x.upper())

Конструкция [... if ... else ... for ...]


Такой код [взятый отсюда] не скомпилируется:
l = [b if char == "w" else 1 for char in input()]
Необходимо добавить скобки:
l = [(b if char == "w" else 1) for char in input()]

Поддерживаемые модули


На данный момент транспайлер Python → 11l → C++ частично поддерживает следующие встроенные модули Python:

Практика


Попробуйте исправить данный Python-код так, чтобы он корректно скомпилировался транспайлером Python → 11l → C++:
  1. Mocha и прогулка
  2. Mocha и Diana (простая версия)
  3. Запрещённая подпоследовательность
  4. Задача про НОД
  5. Mocha и красное и синее
  6. Симпатичные перестановки (подсказка: {})
  7. Плей-офф турнир
  8. Парный платёж

Для проверки вашего решения откройте страницу соответствующей задачи (в таблице под словом ‘Задача’), сохраните входные данные примера в файл
input.txt
и выполните в командной строке следующие 3 команды:
...11l your_solution.py
python orig_solution.py < input.txt > output_py.txt
your_solution < input.txt > output.txt
А затем сравните содержимое файлов
output_py.txt
и
output.txt
.