Error using Argv while running python in terminal - python

I use argv to pass in unique arguments to python scripts while in terminal. Very useful when running same program on multiple unique files (this program parses an xml file for certain things). I have built three different programs that serve unique purposes.
I am aggregating my programs to one .py file so that I can use 'import' within the actual running instance of python and then go one by one through the files:
>>>import xml
>>>xml.a()
>>>xml.b()
>>>xml.c()
How can I pass arguments to these programs on the fly? I'm getting a syntax error when I place the arguments after calling the program in this way.
>>>xml.a() file1.xml file1.csv
^
>>>SyntaxError: invalid syntax

You pass arguments to functions in Python by placing them between the parentheses (these are called "parameters," see documentation). So you'll need to modify your functions so that they can take arguments (rather than read from sys.argv), and then run the function like:
my_library.py
def function1(filename):
print filename
Interpreter
>>> import my_library
>>> my_library.function1("file1.xml")
>>> file1.xml
If you want your function be able to process an indefinite number of arguments (as you can with sys.argv), you can use the * syntax at the end of your arguments list to catch the remaining parameters as a list (see documentation). For example:
my_library.py
def function1(*filenames):
for filename in filenames:
print filename
Interpreter
>>> import my_library
>>> my_library.function1("file1.xml", "file2.csv", "file3.xml")
file1.xml
file2.csv
file3.xml

Related

How can I make one python file ask for user input and then pass this input to a second .py file?

I have an analyze.py file that performs three steps:
it imports a .csv file as a numpy array;
it asks for user input
(e.g. x=input ('Enter a number by which you want to multiply your array?')
uses that input to perform some array operations
(e.g. output_array=csv_array*x).
After step 3, I would like to close the existing window and automatically run a second .py file which imports the new array (output_array).
In other words, I would like to pass a user-input variable from one py file to another. How can I do it?
Please note that I am aware of similar questions (e.g. How can I make one python file run another?), but I cannot guess how to deal with user input.
There are several ways to approach this problem.
Use script args
Write to file and read the same file
Using message queues and workers (Much more complicated)
I would suggest you go with the first approach and use arguments for the second script. You can call you script like this: python script2.py arg1 arg2 and use argsv in the script2 to read related arguments.
$ python script2.py arg1 arg2
>>> import sys
>>> len(sys.argv) # 3
One way is to use subprocess.run to execute the second python file. This way you can pass the input array as argument to the second python file.
from subrocess import run
command = []
command.append(PYTHON_EXECUTABLE_PATH)
command.append(SECOND_FILE_PATH)
command.append(array_converted_to_string) # e.g. separate array elements with dashes '-'
completed_process = run(command, capture_output=True, text=True)
So three things for you to implement:
a function to convert an array to a string
a function to do the conversion back
argument parsing in the second python file # standard library has the argparse module for that

Python; how to properly call another python script as a subprocess

I know a very similar question has already been asked but since none of the solutions posted there worked for my problem I try to make it replicable:
So I'm calling this script to merge some shapefiles (all files in one folder) like this:
shpfiles = 'shapefile_a.shp shapefile_b.shp'
subprocess.call(['python', 'shapemerger.py', '%s' % shpfiles])
I only get the Usage Instructions from the script so I cant determine what goes wrong. If i call the script directly in the terminal it works.
Any help is appreciated.
Every time a program is started, it receives a list of arguments it was invoked with. This is often called argv (v stands for vector, i.e. one-dimensional array). The program parses this list, extracts options, parameters, filenames, etc. depending on its own invocation syntax.
When working at the command line, the shell takes care of parsing the input line, starting new program or programs and passing them their argument list.
When a program is called from another program, the caller is responsible to provide the correct arguments. It could delegate this work to shell. The price for it is very high. There is substantial overhead and possibly a security risk! Avoid this approach whenever possible.
Finally to the question itself:
shpfiles = 'shapefile_a.shp shapefile_b.shp'
subprocess.call(['python', 'shapemerger.py', '%s' % shpfiles])
This will invoke python to run the script shapemerger.py with one argument shapefile_a.shp shapefile_b.shp. The script expects filenames and receives this one name. The file "shapefile_a.shp shapefile_b.shp" does not exist, but the script probably stops before attempting to access that file, because it expect 2 or more files to process.
The correct way is to pass every filename as one argument. Assuming shpfiles is a whitespace separated list:
subprocess.call(['python', 'shapemerger.py'] + shpfiles.split())
will generate a list with 4 items. It is important to understand that this approach will fail if there is a space in a filename.

Allow argument/option to override positional argument

I am trying to make exclusion in my argparse parser. Basically what I want is to avoid --all option and filenames argument to be parsed (which I think succeeded).
But I want to create also another check where if I only pass python reader.py read --all, the filenames argument will get populated with all txt files in current directory.
So far I've come up with following code:
import argparse
import glob
parser = argparse.ArgumentParser()
subcommands = parser.add_subparsers(title='subcommands')
read_command = subcommands.add_parser('read')
read_command.add_argument('filenames', type=argparse.FileType(), nargs = '+')
read_command.add_argument('-a', '--all', action='store_true')
parsed = parser.parse_args()
if parsed.all and parsed.filenames:
raise SystemExit
if parsed.all:
parsed.filenames = glob.glob('*.txt')
print parsed
The problem is that if I try to run python reader.py read --all I get error error: too few arguments because of the filenames argument.
Is there a way to have this work like I want to without creating subcommand to read, for example python reader.py read all?
How can I access error messages in argparse? I'd like to have some default message that would say that filenames and --all can't be combined instead of SystemExit error.
Also I want to avoid using add_mutually_exclusive_group because this is just a snippet of my real parser where this approach wouldn't work (already checked in other SO topic).
I've heard about custom actions but it would really help to see example on it.
If filenames gets nargs="*", it should allow you to use --all alone. parsed.filenames will then be a [], which you can replace with the glob.
You could also test giving that argument a default derived from the glob - but see my caution regarding FileType.
Do you want the parser to open all the filenames you give it? Or would you rather open the files latter yourself (preferably in a with context). FileType opens the files (creating if necessary), and in the process checks their existence (which is nice), but leaves it up to you (or the program exit) to close them.
The documentation talks about issuing error messages yourself, and how to change them. parser.error('my message') with display the usage and message, and then exit.
if parsed.all and parsed.filenames:
parsed.error("Do you want to read ALL or just %s?"%parsed.filenames)
It is also possible trap SystemExit exceptions in a try/except clause.

Use python exec safely

I would like to read a set of variable definitions from a file. I would like to use execfile to read them (to simplify my input code). Consider:
#! /usr/bin/python
from math import *
import os
cmd="""
a=[0,3]
b=[0,1]
print 'Hello'
print sin(2)
os.system('rm my_important_file')
"""
gd={}
ld={}
exec(cmd,gd,ld)
print ld
(I use here exec instead of execfile to simplify the question). As you see I am trying to use exec safely by supplying dictionaries as second and third argument. I would like to only have variable definitions as valid operations in the input file. So print 'Hello', print sin(2), and os.system('rm my_important_file') should all produce errors. It seems to work for the last two, but not for print 'Hello'. What is the reason for this?
print is a statement, a language feature. No imports are required to execute it. You are executing all valid Python code when using exec or execfile, and that includes the print statement.
Your empty dictionaries will also not prevent imports. I can still do:
import os
os.system('rm my_important_file')
in the config file and have it executed under the privileges of the Python code that called execfile() on this file.
If your config file is only allowed to use assignments and a subset of expressions, don't use execfile or exec. There is no way of making those 'safe'; Python is too dynamic a language.
Parse the file yourself into a domain specific language, or use a different pre-existing config file format, such as ConfigParser. These won't allow for Python expressions to be executed, however.

Python script argument conditional

Is anyone able to tell me how to write a conditional for an argument on a python script? I want it to print "Argument2 Entered" if it is run with a second command line arguments such as:
python script.py argument1 argument2
And print "No second argument" if it is run without command line arguments, like this:
python script.py argument1
Is this possible?
import sys
if len(sys.argv)==2: # first entry in sys.argv is script itself...
print "No second argument"
elif len(sys.argv)==3:
print "Second argument"
There are many answers to this, depending on what exactly you want to do and how much flexibility you are likely to need.
The simplest solution is to examine the variable sys.argv, which is a list containing all of the command-line arguments. (It also contains the name of the script as the first element.) To do this, simply look at len(sys.argv) and change behaviour based on its value.
However, this is often not flexible enough for what people expect command-line programs to do. For example, if you want a flag (-i, --no-defaults, ...) then it's not obvious how to write one with just sys.argv. Likewise for arguments (--dest-dir="downloads"). There are therefore many modules people have written to simplify this sort of argument parsing.
The built-in solution is argparse, which is powerful and pretty easy-to-use but not particularly concise.
A clever solution is plac, which inspects the signature of the main function to try to deduce what the command-line arguments should be.
There are many ways to do this simple thing in Python. If you are interested to know more than I recommend to read this article. BTW I am giving you one solution below:
import click
'''
Prerequisite: # python -m pip install click
run: python main.py ttt yyy
'''
#click.command(context_settings=dict(ignore_unknown_options=True))
#click.argument("argument1")
#click.argument("argument2")
def main(argument1, argument2):
print(f"argument1={argument1} and argument2={argument2}")
if __name__ == '__main__':
main()
Following block should be self explanatory
$ ./first.py second third 4th 5th
5
$ cat first.py
#!/usr/bin/env python
import sys
print (len(sys.argv))
This is related to many other posts depending upon where you are going with this, so I'll put four here:
What's the best way to grab/parse command line arguments passed to a Python script?
Implementing a "[command] [action] [parameter]" style command-line interfaces?
How can I process command line arguments in Python?
How do I format positional argument help using Python's optparse?
But the direct answer to your question from the Python docs:
sys.argv -
The list of command line arguments passed to a Python script. argv[0] is the script name (it is operating system dependent whether this is a full pathname or not). If the command was executed using the -c command line option to the interpreter, argv[0] is set to the string '-c'. If no script name was passed to the Python interpreter, argv[0] is the empty string.
To loop over the standard input, or the list of files given on the command line, see the fileinput module.

Categories