4
\$\begingroup\$

When I used to use a PC I was familiar with py2exe, the mac equivalent I saw most people talking about was py2app — which compiles python to .app files, not .exec files. I was familiar with compiling C files to execs on mac, and I had heard of cython (but I had never used it before), so I figured I could write something to go from python to cython to an exec. I initially did not write this for anyone else to use — but now I'm thinking about packaging it as a pip module.

This is my first time building a command line tool for executing shell commands and messing with files and directories.

"""(PYCX) PYthon to Cython to eXec, a unix command line util
Usage:
 pycx FILES... [-o DIR --show --delete --run]
 pycx --help
Options:
 FILES one or more python files to compile
 -o --output=DIR output directory
 -s --show show output from exec compiling
 -d --delete delete the c file after compiling exec
 -r --run run the exec after compiling
 -h --help show this screen.
"""
import os, re
from docopt import docopt
args = docopt(__doc__)
# the two pathnames below tell gcc where python is so that cython can be compiled to an exec
INCLUDES = '/usr/local/opt/python/Frameworks/Python.framework/Versions/3.7/include/python3.7m'
LIBRARY = '/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib'
HIDEDATA = '&>/dev/null' # this is used to hide output while compiling C files
for pyFILE in args['FILES']:
 if pyFILE.endswith('.py'): # file must be a python file
 path, name = os.path.split(pyFILE) # split full path to seperate path & filename
 cFILE = re.sub('\.py$', '.c', pyFILE) # name of the file with .c extension & path
 FILE = re.sub('\.py$', '', name) # name of the file with no extension or path
 PATH = path + '/' if path is not '' else '.' # if in current directory, path = '.'
 OUTPUT = args['--output'] + '/' if args['--output'] else '' # blank if no arg given
 SHOW = HIDEDATA if not args['--show'] else '' # will hide gcc output if SHOW is false
 # this command will be used to delete the C file
 DELETE = f'find {PATH} -name "{FILE}.c" -type f|xargs rm -f' if args['--delete'] else ''
 RUN = f'./{OUTPUT}{FILE}' if args['--run'] else '' # command to run the exec
 commands = [ # cython to make C file, gcc to compile to exec, and some options
 f"cython --embed -o {cFILE} {pyFILE}", # convert python to cython C file
 # compile cython C file to exec file
 f"gcc -v -Os -I {INCLUDES} -L {LIBRARY} {cFILE} -o {OUTPUT}{FILE} " + \
 # source python & other options -- hide or show, delete C file, run exec
 f"-lpython3.7 -lpthread -lm -lutil -ldl {SHOW}", f"{DELETE}", f"{RUN}"
 ]
 for command in commands:
 os.system(command) # execute commands above, excluding blank commands
 else:
 print(__doc__) # show the help menu if user doesn't put a python file

Besides modifying INCLUDES and LIBRARY to work with varying path locations, are there any other major problems preventing this from working as a python module? Does this even seem like a tool some people might use?

asked Mar 29, 2019 at 22:43
\$\endgroup\$

2 Answers 2

5
\$\begingroup\$

I recommend that you read PEP 8.

  • It's advised against doing import os, re. Instead have one line dedicated to each import.
  • You should have an empty line between importing third party libraries and standard library ones.
  • In Python it's standard to use snake_case.
  • You should only have one space either side of the equals sign when performing assignment.
  • Rather than doing path is not '' you should use the != operator.
  • '\.py$' is deprecated in Python 3.6, and will become a syntax error in a future version of Python. Instead use r'\.py$'.
  • Don't mix ' and " string delimiters. Unless it's to do "'".
  • You don't need to use a \ for line breaking as your within brackets.
  • You should have the + in front of the next line, rather than the end of the previous line.

  • Your variable names are crap. path, name FILE, cFILE, PATH. The reason you need your comments is because of this.
  • If you fix your variable names your comments are useless.
  • Don't make a list to then call os.system just call it straight away.
  • Use pathlib, it singlehandedly makes your code ridiculously simple.
  • Put your code in an if __name__ == '__main__' guard. You don't want to destroy things by accident.

untested:

"""(PYCX) PYthon to Cython to eXec, a unix command line util
Usage:
 pycx FILES... [-o DIR --show --delete --run]
 pycx --help
Options:
 FILES one or more python files to compile
 -o --output=DIR output directory
 -s --show show output from exec compiling
 -d --delete delete the c file after compiling exec
 -r --run run the exec after compiling
 -h --help show this screen.
"""
import os
from pathlib import Path
from docopt import docopt
INCLUDES = '/usr/local/opt/python/Frameworks/Python.framework/Versions/3.7/include/python3.7m'
LIBRARY = '/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib'
HIDEDATA = '&>/dev/null'
def main(args):
 for path in args['FILES']:
 path = Path(path)
 if path.suffix != '.py':
 print(__doc__)
 continue
 output = Path(args['--output'] or '') / path.stem
 c_file = path.parent / path.stem + '.c'
 os.system(f'cython --embed -o {c_file} {path}')
 os.system(
 f'gcc -v -Os -I {INCLUDES} -L {LIBRARY} {c_file} '
 f'-o {output} -lpython3.7 -lpthread -lm -lutil -ldl '
 + HIDEDATA if not args['--show'] else ''
 )
 if args['--delete']:
 os.system(
 f'find {path.parent} -name "{path.stem}.c" -type f'
 f'|xargs rm -f'
 )
 if args['--run']:
 os.system(f'{output}')
if __name__ == '__main__':
 main(docopt(__doc__))
answered Mar 30, 2019 at 2:57
\$\endgroup\$
3
  • \$\begingroup\$ you should use the != operator Eh, not really. I posted a small answer on that in particular. Otherwise this seems sane. \$\endgroup\$ Commented Mar 30, 2019 at 20:46
  • \$\begingroup\$ Would c_file = path.parent / (path.stem + '.c') do something different then what you have? It seems like this works better. Thanks for your valuable tips! \$\endgroup\$ Commented Mar 31, 2019 at 0:21
  • \$\begingroup\$ @Reinderien I'd like to point out that that is only talking about the is operator in that line, not the turnery logic too. Hence why it's in the pep8 section of my answer and changed to use pathlib and an or in my code. \$\endgroup\$ Commented Mar 31, 2019 at 12:12
1
\$\begingroup\$

This code is obviated by @Peilonrayz, which has posted a good solution. However, in the future, if you need to do something like

PATH = path + '/' if path is not '' else '.'

you're better off to do

PATH = (path or '.') + '/'

Otherwise, the other suggestion is complete.

answered Mar 30, 2019 at 20:45
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Yes! After editing my code based on his suggestions I was beginning to realize I can do this variable assignments, I'm glad I know this now. Thanks for the tip! \$\endgroup\$ Commented Mar 31, 2019 at 0:17

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.