Translating LaTeX documents into different languages using DeepL API I'm coming from C and C++ and have often troubles using Python's full potential. This is a script I wrote to translate LaTeX documents into different languages. A parser replaces all latex specific syntax with hashes and then sends it all to deepl. The pydeepl wrapper is from Github.

What do you think of this code?

import sys
import re
import pydeepl
from random import choice
from tqdm import tqdm

def make_xlat(*args, **kwds):
    adict = dict(*args, **kwds)
    rx = re.compile('|'.join(map(re.escape, adict)))
    def one_xlat(match):
        return adict[match.group(0)]
    def xlat(text):
        return rx.sub(one_xlat, text)
    return xlat 

if __name__ == "__main__":
     fileInputName = sys.argv[1]
     #fileInputName = "Introduction.tex"
     fileOutName = fileInputName.split('.')[0]+"_trans.tex"

     fileIn  = open(fileInputName, "r")
     fileOut = open(fileOutName, "w")

     fileStr = fileIn.read()

     print("Starting hashing...")

     #replace commands like \begin{*}, \end{*}, tabs etc. with hashes
     searchObj1 = re.findall( r"\\begin\{\w+\}|\t|    |\r|\\end\{\w+\}|\\usepackage\{\w+\}|\\newcommand\{\w+\}|\\include\{.*\}|\\input\{\w+\}|\\\w+\[.*\}|\%.*", fileStr)
     #random number for every found command + a prefix which hopefully doens't appear in text. Used to skip lines later, which don't need translation
     list1 = ['X#X' + str(choice(range(1111, 9999, 1))) for x in searchObj1]
     #make a dictionary out of hashes
     d1 = dict(zip(searchObj1,list1))
     translate = make_xlat(d1)
     hashedText = translate(fileStr)

     #replace all latex commands (starting with a backslash) with hashes
     searchObj2 = re.findall( r"\\\w+",hashedText)
     #random number  + prefix again
     list2 = ['X#X' + str(choice(range(1111, 9999, 1))) for x in searchObj2]
     #make a dictionary
     d2 = dict(zip(searchObj2,list2))
     translate = make_xlat(d2)
     hashedText = translate(hashedText)
     #print(hashedText)
     #fileOut.write(translate(hashedText))

     d1.update(d2) # combine dictionaries
     #with open('hash_dict.json', 'w') as f:
     #json.dump(d1, f)

     print("Hashing done. Starting translation...")

     translated = ''
     for line in tqdm(hashedText.splitlines()): #tqdm is a progressbar
         #print(line)
         if line.startswith("X#X") and len(line) == 7:
             translated = translated + line + '\n'
             continue
         if line == '\n':
             translated = translated + '\n'
         elif line == '':
             translated = translated + '\n'
         else:
             translated = translated+pydeepl.translate(line, "DE", "EN")+'\n'
     #translated = translated+pydeepl.translate(hashedText, "DE", "EN")
     #print(translated)

     d1Inv = {val:key for (key, val) in d1.items()} #swap dictionary
     translate2 = make_xlat(d1Inv)
     fileStrOut = translate2(translated)
     #print(fileStrOut)

     fileOut.write(fileStrOut)

     print("success")
     fileIn.close()
     fileOut.close()


EDIT 1: One flaw I already know is that I'm sending sentences by sentences which makes it really slow. However, sending all lines at once produces errors which I couldn't get rid of. Deepl scrambles stuff around and dehashign doesn't work anymore.
While using sys.argv is fine for a first version of a program, you should give it a proper CLI. It would be nice if multiple latex files could be passed (since it is common to split a large project into multiple files) and also to specify the source and target languages. For this you can use argparse:

import argparse

def parse_args(args=None):
    parser = argparse.ArgumentParser()
    parser.add_argument("--from", default="DE", help="Language of the source document(s)")
    parser.add_argument("--to", default="EN", help="Language of the target document")
    parser.add_argument("file", nargs="+", help="Path(s) to the latex file(s)")
    return parser.parse_args(args)

if __name__ == "__main__":
    args = parse_args()
    print(args.to, args.from, args.file)
    ...


This even gives you a nice usage message when calling the script with the option -h or --help:

usage: script_name.py [-h] [--from FROM] [--to TO] file [file ...]

positional arguments:
  file         Path(s) to the latex file(s)

optional arguments:
  -h, --help   show this help message and exit
  --from FROM  Language of the source document(s)
  --to TO      Language of the target document

Now, let's get to your "hashing". When randomly selecting from \$H = 9999 - 1111 = 8888\$ values, it takes on average \$Q(H) = \sqrt{\tfrac{\pi}{2}H}\approx 118.16\$ draws until you get a number twice. In other words, if you have more than about 120 elements, you will overwrite some. And that number is actually less than half of that because you separately hash \begin and \end.

One way to avoid collisions is to just keep on counting up. For this you could use itertools.count, which produces an infinite stream of increasing numbers, starting with the argument (or 0 if not given), when being iterated:

from itertools import count

counter = count(1111)
assert len(searchObj1) + len(searchObj2) &lt;= 8888, "Too many objects to hash"
list1 = ['X#X{}'.format(i) for _, i in zip(searchObj1, counter)]
list2 = ['X#X{}'.format(i) for _, i in zip(searchObj2, counter)]


Another way to make it less likely is to extend the range. And at that point you might as well use the built-in hash function:

list1 = ['X#X{}'.format(hash(x)) for x in searchObj1]


In this case you would need to relax the and len(line) == 7 requirement later.
When opening files, you should use the with keyword to ensure they are properly closed, even if an exception occurs somewhere in the block:

with open(fileInputName) as fileIn, open(fileOutName, "w") as fileOut:
    ...

You should also think about better names. Python has an official style-guide, PEP8, which recommend using lower_case instead of camelCase. In addition, list1, searchObj1, d2 are all not very good names.
Doing string addition can be very costly, because in Python strings are immutable. This means that when you do str_a + str_b, Python has to allocate a new string object of length len(str_a) + len(str_b) and then copy the content of both strings into that new array. This takes more time the longer the strings are and the more often you do it. Since you are adding strings of the length of a full document and are doing so in a loop, this can be very slow.

Instead, build a list and str.join it at the end:

translated = []
for line in fileIn:
    #print(line)
    if line.startswith("X#X") and len(line) == 7:
        translated.append(line)
    elif not line.strip():
        translated.append('')
    else:
        translated.append(pydeepl.translate(line, args.from, args.to))
translated = '\n'.join(translated)


Note that I directly iterate over the file (since files are iterable), meaning that this program is less limited by the amount of memory available. However this means that the progress bar does not work anymore (and I therefore removed it here), because it needs to know how many lines there are. You can use something like this to add it back: https://blog.nelsonliu.me/2016/07/29/progress-bars-for-python-file-reading-with-tqdm/