Posts: 3
Threads: 2
Joined: Jun 2023
I am new to python...
I am trying to call a function defined in a separate program, but I cannot figure out how to pass an argument. How to do?
The function I want to call is in this program:
testren.py:
import sys, getopt
global old_name
def renameit(argv):
global old_name
old_name = ''
opts, args = getopt.getopt(argv,"ho:",["oldname="])
for opt, arg in opts:
if opt in ("-o", "--old_name"):
old_name = arg
print ('in testren after - old_name =', old_name )
if __name__ == "__main__":
print ('sys.argv ', sys.argv[1:])
renameit(sys.argv[1:]) If I call testren.py directly, I get the desired results:
Output: C:\python>python testren.py -o "my1.mp3"
sys.argv ['-o', 'my1.mp3']
in testren after - old_name = my1.mp3
If I call it from another python program the arguments are not found, I cannot figure out how to pass the arguments. How to do?
calltestren.py:
import testren
if __name__ == '__main__':
renargs = '-o "my1.mp3"'
print('calling rename', renargs)
testren.renameit(renargs)
print('old_name<', old_name, '>') I just get blanks where I expect a file name to be:
Output: C:\python>python calltestren.py
calling rename -o "my1.mp3"
in testren after - old_name =
old_name< >
Posts: 4,786
Threads: 76
Joined: Jan 2018
Jun-23-2023, 07:36 AM
(This post was last modified: Jun-23-2023, 07:41 AM by Gribouillis.)
Some remarks first:
- Avoid using the
global keyword as much as possible, it makes better code. Seasoned pythonistas use this keyword very infrequently.
- Don't use the
getopt module, use the more flexible argparse module instead. Getopt just mimics the old getopt library of the C language. I think it is still here mostly for backward compatibility.
- The function
renameit expects its first argument to be a list of strings, don't give it a simple string.
Here is the corrected code
# testren2.py
import argparse
import sys
def renameit(argv):
parser = argparse.ArgumentParser()
parser.add_argument(
'-o', '--old-name', help="old name", dest='old_name', required=True)
args = parser.parse_args(argv)
print ('in testren after - old_name =', args.old_name )
if __name__ == "__main__":
print ('sys.argv ', sys.argv)
renameit(sys.argv[1:]) and the code that calls it
# calltestren2.py
import testren2 as testren
if __name__ == '__main__':
renargs = ['-o', 'my1.mp3'] # note this is a list of strings, not a single string!
print('calling rename', renargs)
testren.renameit(renargs)
Posts: 7,315
Threads: 123
Joined: Sep 2016
Jun-24-2023, 01:11 PM
(This post was last modified: Jun-24-2023, 01:11 PM by snippsat.)
(Jun-23-2023, 04:43 AM)Wimpy_Wellington Wrote: If I call it from another python program the arguments are not found, I cannot figure out how to pass the arguments. How to do? I may think there is confusion about using command line tool sys.argv, getopt(do not use as mention).
Because you don't use command line here at all,but try to call it from a other module using a string.
argparse Gribouillis show is ok,but can show a example with Typer which is really cool.
Here i also do rename on files on disk,not just show there names.
from pathlib import Path
import typer
app = typer.Typer()
@app.command()
def renameit(
old_name: str = typer.Option(..., '-o', '--old-name'),
new_name: str = typer.Option(..., '-n', '--new-name'),
):
old_path = Path(old_name)
if old_path.is_file():
old_path.rename(Path(new_name))
typer.echo(f'File renamed from {old_name} to {new_name}')
else:
typer.echo('File not found.')
if __name__ == '__main__':
app() Using it,see that help and colors get(use Rich under the hood) get generated automatic.
![[Image: ATaLiF.png]](https://imagizer.imageshack.com/v2/xq70/924/ATaLiF.png)
Also using this in a other module only need to pass app ,and it will work.
from rename_files import app
if __name__ == '__main__':
app()
Posts: 1,093
Threads: 143
Joined: Jul 2017
I don't know about the theoretical aspects of programming, or technical stuff. For me Python is just a practical tool. Get the job done as simply as possible.
I have a folder: /home/pedro/myPython/myModules/ where, unsurprisingly, I keep my homemade modules.
If I make this module and save it in /home/pedro/myPython/myModules/ as change_file_names.py:
import os
def change_name(a,b):
os.rename(a, b)
print('Changed the name of', a, 'to', b) Then I can call it anytime:
#! /usr/bin/python3
# do this because I don't add the path to my modules to the system PATH variable
import sys
sys.path.append('/home/pedro/myPython/myModules/')
# if you add the path to your homemade modules to your system PATH, you can forget the 2 lines above
import change_file_names as cfns
if __name__ == "__main__":
cfns.change_name('/home/pedro/temp/a.txt', '/home/pedro/temp/b.txt') I'm sure the suggestions from Gribouillis and snippsat are technically much better, but it's hard for me to understand what's going on, it's all very cryptic.
Also, if the trusty old steed os can do the job, why complicate matters? Just use os.
Posts: 4,786
Threads: 76
Joined: Jan 2018
Jul-05-2023, 07:39 AM
(This post was last modified: Jul-05-2023, 07:39 AM by Gribouillis.)
(Jul-04-2023, 11:18 PM)Pedroski55 Wrote: sys.path.append('/home/pedro/myPython/myModules/') @ Pedroski55 you could write this line in a (new?) file named usercustomize.py in the directory returned by site.getusersitepackages()
This would spare you the effort to write this line in all your scripts.
Also the PATH variable is for executable programs, not for Python modules.
Posts: 1,093
Threads: 143
Joined: Jul 2017
Aha! Technical stuff! Thanks!
That was easier than I thought:
import site
site.addsitedir('/home/pedro/myPython/myModules/') Then this works fine:
import change_file_names as cfns
if __name__ == "__main__":
cfns.change_name('/home/pedro/temp/b.txt', '/home/pedro/temp/a.txt') But I prefer
import os
os.rename('/home/pedro/temp/a.txt', '/home/pedro/temp/b.txt')
Posts: 6,785
Threads: 20
Joined: Feb 2020
Jul-05-2023, 06:33 PM
(This post was last modified: Jul-05-2023, 06:55 PM by deanhystad.)
Why is testren.py a module? It is not reusable code. How can a different Python program use this code? There are reusable command line parsers (typer was mentioned. I like argparse for simple command lines. There are others) that let you parse arbitrary command lines. Why write a module for doing this? I can understand writing this as a function in your main program, but it makes no sense as a module.
Never use global variables to return values from a function. Use the return statement. The less you have to know about a function/module to use it, the better. Your users should not have to call a function and then access a global variable to get the result. That forces them to know 3 things (function, variable, connection between the two). Your function should return the value directly, so your users only need to know about the function.
Maybe I am reading this wrong and renameit() is supposed to be a reusable function that does something useful (like rename a file from old_name to new_name). If that is the case, you should pass function arguments, not command line arguments. You can also write your module so it lets you call renameit() from the command line. This is a simple example.
renameit.py
import sys
# This is the reusable function I want to export.
def renameit(old_name, new_name):
"""Does something interesting or useful with old_name and new_name."""
print(f"renameit({old_name}, {new_name})")
# This lets me execute the function from the command line.
if __name__ == "__main__":
args = sys.argv[1:]
if len(args) != 2:
print("Missing arguments for renameit file new_name")
else:
renameit(*args) Importing and using the renameit function.
from renamit import renameit
renamit("name of old thing", "the new name") Here I added an optional argument and use argparse to parse the command line.
import argparse
def renameit(old_name, new_name, version=None):
"""Does something interesting or useful with old_name and new_name."""
print(f"renameit({old_name}, {new_name}, version={version})")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Give an old thing a new name.")
parser.add_argument("old_name", type=str, help="name of existing thing")
parser.add_argument("new_name", type=str, help="new name for thing")
parser.add_argument(
"-v",
"--version",
action="store_true",
help="add version number of new name clashes with existing thing",
)
args = parser.parse_args()
renameit(args.old_name, args.new_name, args.version) Now I can ask for help on how to use the function from the commandline.
Output: : python renameit -h
usage: renameit.py [-h] [-v] old_name new_name
Give an old thing a new name.
positional arguments:
old_name name of existing thing
new_name new name for thing
options:
-h, --help show this help message and exit
-v, --version add version number of new name clashes with existing thing
And if I mess up entering the arguments I get an informative error message.
Output: : python renameit.py -v "name of existing thing"
usage: renameit.py [-h] [-v] old_name new_name
junk.py: error: the following arguments are required: new_name
|