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?
2 Answers 2
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 user'\.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__))
-
\$\begingroup\$
you should use the != operator
Eh, not really. I posted a small answer on that in particular. Otherwise this seems sane. \$\endgroup\$Reinderien– Reinderien2019年03月30日 20:46:52 +00:00Commented 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\$Shui– Shui2019年03月31日 00:21:45 +00:00Commented 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\$2019年03月31日 12:12:23 +00:00Commented Mar 31, 2019 at 12:12
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.
-
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\$Shui– Shui2019年03月31日 00:17:22 +00:00Commented Mar 31, 2019 at 0:17