en | ru |
Guide to using the Python → 11l → C++ transpiler
Here [in this guide] we discuss the features of the Python → 11l → C++ transpiler that must be taken into account when writing Python code, so that the transpiler compiles it correctly.
Integers
The Python programming language is commonly known to use arbitrary-precision integers. On the one hand, this means the programmer does not have to think about what size of number may be stored in a variable of integer type. On the other hand, however, working with arbitrary-precision numbers is much more expensive and inefficient. Therefore, the Python → 11l → C++ transpiler treats all integer numbers as 32-bit integers (the default in C++). If bigger integers (for example, 64-bit) are required, then you can either explicitly specify the type of the variable as
Int64, or use the transpiler option
--int64on the command line (in this case, all integers will be treated as 64-bit rather than 32-bit). An explicit indication of a variable's type looks like this:
s : Int64 = 0But Python has no built-in
Int64type. Therefore, if you want the code to work in Python, you need to add the following line once at the beginning of the program:
Int64 = intThe Python → 11l → C++ transpiler will understand the Python source code even without that line, however (and if does encounter such a line it will simply ignore it).
If an arbitrary-precision integer is required, then type
BigIntshould be used.
Character variables
As with 64-bit integers, the Python → 11l → C++ transpiler provides the ability to declare character variables, that is, variables whose value is a single character code. The type for character variables is
Char. And as with
Int64, to get code that works in Python you should include this line:
Char = strAn array of characters can be declared like this:
charr : List[Char] = []This can be used both to improve performance and to reduce RAM usage (especially if a very large array of characters is needed, for example).
Creating an empty list/array, dictionary or set
Unlike Python, containers (such as arrays and sets) in 11l and C++ can only contain elements of a single type (with the exception of tuples, which can contain elements of different types). The type must be known at compile time. Currently, the Python → 11l → C++ transpiler does not try to determine the type of containers from their usage, as is done in some programming languages (e.g. Nemerle), so when you create an empty container, you must specify the type of its elements explicitly.
Not supported | Supported |
---|---|
l = [] | l : List[int] = [] or l = [0] * 0 |
d = {} | d : Dict[str, int] = {} |
s = set() | s = set() # int |
dd = collections.defaultdict(int) | dd = collections.defaultdict(int) # str |
collections.defaultdictdictionary is a list, then the following notation must be used:
dd : DefaultDict[str, List[int]] = collections.defaultdict(list)
Creating a non-empty list/array, dictionary or set
If a container is initialized with elements, then you do not need to specify the type:
l = [1, 2, 3] d = {'a': 1, 'b': 2} s = {1, 2, 3}Since all elements of a list must be of the same type, a list like this [taken from here] will not compile:
x = [0, 1, 2, 2, 2, 2, 1, 9, 3.5, 5, 8, 4, 7, 0, 6]To fix it, just specify the type of the first element:
x = [float(0), 1, 2, 2, 2, 2, 1, 9, 3.5, 5, 8, 4, 7, 0, 6]
Passing a list as an argument to a function
In Python, when a variable of type list is passed to a function, it can then be changed inside the function. In order to retain the ability to change the variable, the Python → 11l → C++ transpiler requires the type of this argument to be specified explicitly.
Thus, the function declaration
def decompress(compressed)in the solution to the LZW compression task needs to specify the argument type:
def decompress(compressed : List[int]): ...Or like this:
def decompress(compressed : list): ...
Class member variables
For the Python → 11l → C++ transpiler to successfully compile the code declaring a new class, the types of all the class's member variables must be specified.
Will not compile | Will compile |
---|---|
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 |
String concatenation
Since the
+operator cannot be used for string concatenation in 11l (for the reasons set out in the language documentation), while the language instead uses its own syntax for this operation, the Python → 11l transpiler tries to guess when the
+operator is being used arithmetically and when it means string concatenation. In cases where it fails to identify string concatenation correctly, an empty string should be added between the operands (so instead of
str1 + str2you should write
str1 + '' + str2), e.g.:
aa = ['1', '2'] bb = ['x', 'y'] for a in aa: for b in bb: print(a + '' + b)
However, in practice, in most cases where the Python → 11l transpiler fails to detect string concatenation, adding a type annotation is sufficient. For example, in this code:
def rotated(s): return s[1:] + s[0]it is sufficient to specify the type of the argument
s:
def rotated(s : str): return s[1:] + s[0](Instead of writing
s[1:] + '' + s[0].)
[In future versions of the transpiler, the ability to use
+for string concatenation in 11l code may depend on the
--max-compat-with-pythonoption.]
String and character literals
Since in 11l the expression
"A"is context-dependent (it could be either a
Stringor a
Char), the type must be specified explicitly in cases where the transpiler misidentifies the type of a string literal, i.e.
str('A')or
Char('A')should be used. The following Python code will not compile:
print(['AF'] + ['A']*5)It must be written like this:
print(['AF'] + [str('A')]*5)
Also, if you have a variable of type
Char(
ch : Char), then to assign a character value to it you must use
ch = Char('*')instead of
ch = '*'.
Multiple initialization
The Python → 11l → C++ transpiler does not currently support multiple initialization, so instead of
a = b = 0you should use:
a = 0 b = 0(Multiple assignment (e.g.
a[0] = a[1] = 1) is however supported, as are the constructs
if a == b == c ...and
if a < b < c ....)
The from ... import ... statement
The Python → 11l → C++ transpiler does not currently support this statement, so instead of code such as:
from math import sqrt print(sqrt(2))you should use:
import math print(math.sqrt(2))
There are cases, however, where
from ... import ...should be used:
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 *
Recursive function calls
If a function calls itself [recursively], then when it is declared you must explicitly specify the type of the return value (since it cannot be automatically inferred in this case by the C++ compiler).
def find(x, y) -> None: # `-> None` here is required ... find(nx, ny) # because of this call
In the case of a recursive call of a local function, you need to specify not only the return type but also the types of all the function's arguments:
def fib(n): def f(n : int) -> int: # just `def f(n):` # or `def f(n) -> int:` will not work if n < 2: return n return f(n-1) + f(n-2) return f(n)This is due to the particular way the C++ compiler works.
Iterating over elements of a dictionary in a for loop
When you use a
forloop to iterate over a dictionary, if the Python → 11l transpiler cannot identify that the iterable container is a dictionary then you need to specify explicitly that you want to iterate over the keys rather than key-value pairs:
for k in d: # if the type of `d` cannot be determined this will not compile print(k) # In this case, you need to write: for k in d.keys(): print(k)
Division
If neither the divisor nor the dividend is a numeric literal, and both are integers, then division is performed according to the rules of C++ and Python 2, not Python 3, i.e. integer division is used. To obtain real-number division, you must use a cast to a real type (i.e. instead
a/byou need to use
float(a)/bor
a/float(b)), or else use transpiler option
--python-divisionso that the
/operation always follows the rules of Python 3.
Remainder of division
If
a
could be less than 0
, then instead of a % byou should use:
((a % b) + b) % bor
r = a % b; if r < 0: r += bThis is because
%in 11l works [for performance reasons] as in C++, not as in Python.
Alternatively, you can use transpiler option
--python-remainder, in which case the
%operation will work as it does in Python.
The yield statement
The Python → 11l → C++ transpiler does not currently support this statement, so instead of code such as:
def squares(n): for i in range(n): yield i ** 2you should use:
def squares(n): r : List[int] = [] for i in range(n): r.append(i ** 2) return r
Order of evaluation of function arguments
Unlike Python, C++ unfortunately does not define the order in which arguments are evaluated when they are passed to a function, so it is necessary to watch out for possible incorrect behavior of code that modifies shared variables while passing function parameters.
Here is a line of code from an example implementation of an RPN calculator in Python:
a.append(b[c](a.pop(),a.pop()))For it to work correctly with the Python → 11l → C++ transpiler, it needs to be rewritten like this:
t = a.pop() a.append(b[c](t, a.pop()))
Passing a function as an argument to another function
Here is some code from the solution to the "Sort using a custom comparator" task:
def mykey(x): return -len(x), x.upper() print(sorted(strings, key=mykey))It will not compile with the Python → 11l → C++ transpiler, because in the generated C++ code
mykeyis a template function.
You must either specify the types of the function's arguments:
def mykey(x : str): return -len(x), x.upper()Or use a lambda function:
mykey = lambda x: (-len(x), x.upper())
The [... if ... else ... for ...] expression
Code like this [from here] will not compile:
l = [b if char == "w" else 1 for char in input()]Parentheses need to be added:
l = [(b if char == "w" else 1) for char in input()]
Supported modules
Currently, the Python → 11l → C++ transpiler partially supports the following built-in Python modules:
- math
- os
- time
- re
- random
- collections (defaultdict, deque and Counter only)
- heapq
- bisect
- array
- fractions
Practice
Try fixing this Python code so that it compiles correctly with the Python → 11l → C++ transpiler:
- Mocha and Hiking
- Mocha and Diana (Easy Version)
- Forbidden Subsequence
- GCD Problem
- Mocha and Red and Blue
- Pretty Permutations (hint: {…})
- Playoff Tournament
- Paired Payment
To check your solution, open the page for the corresponding problem (under ‘Problem’ in the table), save the input of the example into a file called
input.txtand then run the following three commands from the command line:
...11l your_solution.py python orig_solution.py < input.txt > output_py.txt your_solution < input.txt > output.txtThen compare the contents of the files
output_py.txtand
output.txt.