0

I'm getting into Python, and though of writing my own script which allows me to check arguments provided when a program is run. An example below of what I'm trying to achieve:

python file.py -v -h anotherfile.py
or
./file.py -v -h anotherfile.py

In these two cases, the -v and -h arguments, print out the module version, and a basic help file. I already have the code to differentiate between arguments and files, except I want to create a generalised module on the matter.

The following code written in Java-

// Somewhere.
public static HashMap<String, Runnable> args = new HashMap<String, Runnable>();
public void addArgument(String argument, Runnable command) {
    if (argument.length() > 0) {
        if (args.get(argument) == null) {
            args.put(argument, command);
        } else {
            System.err.println("Cannot add argument: " + argument + " to HashMap as the mapping already exists.");
            // Recover.
        }
    }
}

// Somewhere else.
foo.addArgument("-v", () -> {System.out.println("version 1.0");});
foo.args.get("-v").run();

-will run the Lambda Expressions (atleast that's what I read they were when researching the topic) successfully. I have no idea how Lambda Expressions work however, and have only basic knowledge of using them.

The point of this question, is how can I implement something like the Java example, in Python, storing any type of code inside of an array?

The thing is with the Java example though, if I have int i = 0; defined in the class which executes addArgument and uses i somehow or rather, the class containing addArgument knows to use i from the one that invoked it. I'm worried that this may not be the same case for Python...

I want to be able to store them in dictionaries, or some other sort of key-based array, so I can store them in the following manner:

# Very rough example on the latter argument, showing what I'm after.
addoption("-v", print("version 1.0"))

EDIT: Example of what I want: (not working as is) (please ignore the ;'s)

args = {};

def add(argument, command):
    args[argument] = lambda: command;  # Same problem when removing 'lambda:'

def run():
    for arg in args:
        arg();  # Causing problems.

def prnt():
    print("test");

add("-v", prnt);
run();

4 Answers4

0

In Python, you can simply use the reference to a function as a value in a data structure. E.g., consider the following code:

def spam():
    print('Ham!')

eggs = [spam, spam, spam]
for x in eggs:
    x()

This would call three instances of the function spam.

The lambda expressions you are referring to are simply another way to define functions, without explicitly having to bind them to a name. Consider the following:

eggs = [lambda : print('ham'), lambda : print('spam')]
for x in eggs:
    x()

This gets much more powerful when you're using arguments, though:

fn = [lambda x: return x + 3, lambda x: return x * 2]
for f in fn:
    print(f(4))

Once you have a reference to a function, you can then pass that to another function.

def foo(x):
    return x + 2

bar = lambda x: return x * 2

def h(g, f, x):
    return g(f(x))

print(h(foo, bar, 37))

In your scenario, however, you could simply pass lambda : print("version 1.0").

Joost
  • 4,094
  • 3
  • 27
  • 58
  • That first example! Is there a way I can give `spam()` as an argument to a `def`? –  Jan 19 '16 at 13:28
  • Something like, `def foo(boo): boo()` –  Jan 19 '16 at 13:29
  • Sure (assuming I understood correctly). I'll update my answer. EDIT: Yes, precisely that. – Joost Jan 19 '16 at 13:30
  • Uhhh.. I'm sorta lost in the edit? How does that work? –  Jan 19 '16 at 13:34
  • It shows definitions for the functions `foo` and `bar` (both using `def` and using `lambda`), and then passes these functions to another function, `h`. The function `h` then calls them sequentially (i.e. passes the result from `f` to `g`). The result is printed. When calling `h`, we fill in the function `foo` for `f` and `bar` for `g`, but you can now use `h` to combine any two functions (assuming that the former outputs something the latter can deal with as input). – Joost Jan 19 '16 at 13:35
  • For some reason flake-8 is giving me all these errors when trying to create `bar`. **E731** and **E901**... –  Jan 19 '16 at 13:39
  • Rightfully so; I only wrote it out like that to show the versatility of function definitions, but using a lambda like that has no advantages over `def`, which is why PEP8 disapproves. See [this question](https://stackoverflow.com/questions/25010167/e731-do-not-assign-a-lambda-expression-use-a-def). – Joost Jan 19 '16 at 13:43
0

EDIT: to correct your most recent code, iterating over a dictionary yields its keys not its values, so you need:

args = {};

def add(argument, command):
    args[argument] = command;

def run():
    for arg in args:
        args[arg]()     # <<<<<<<<<<

def prnt():
    print("test");

add("-v", prnt);
run();

For the example you give, a dictionary of functions, although possible, isn't really the natural way to go about things in Python. The standard library has a module, argparse specifically for dealing with all things commmand-line argument related, and rather than using keys to a dictionary you can parse the command line arguments and refer to their stored constants.

import argparse

def output_version():
    print('Version 1.0!')

parser = argparse.ArgumentParser(description="A program to do something.")
parser.add_argument('-v', help='Output version number.', action='store_true')

args = parser.parse_args()
if args.v:
    output_version()

(In fact outputting a version string is also handled natively by argparse: see here).

To print out the contents of a text file, myfile.txt when the -p switch is given:

import argparse

def print_file(filename):
    with open(filename) as fi:
        print(fi.read())

parser = argparse.ArgumentParser(description="A program to do something.")
parser.add_argument('-p', help='Print a plain file.', action='store')

args = parser.parse_args()

if args.p:
    print_file(args.p)

Use with e.g.

$ prog.py -p the_filename.txt
xnx
  • 24,509
  • 11
  • 70
  • 109
  • How would I print the contents of a plain file as an action for an argument? –  Jan 19 '16 at 13:32
  • You can define a function to do that and then call it when the argument parser contains the corresponding switch. Do you want to specify the filename on the command line too? – xnx Jan 19 '16 at 13:35
  • Definitely. I'm creating a generic module, in which I can **specify any** actions for **any and all** arguments **and files**. –  Jan 19 '16 at 13:37
  • You can certainly specify which filename to pass to the print function (see my edit)... but perhaps I don't understand: do you want to pass code to be executed on the command line? – xnx Jan 19 '16 at 13:41
  • Well, like in my Java example, that lambda function is generic. I can make it jump up and down, do a cartwheel, just by typing it. <- Is my goal. Yours has great command-line support which I've just tested now, except you are manually specifying what `args.p` does. –  Jan 19 '16 at 13:43
  • I want to include what `args.p` does as an argument to a function inside the module i'm creating, thus eliminating any other work other than `addargument("-p", print_file(args.p))` –  Jan 19 '16 at 13:44
  • In your edit you need to pass the actual function objects you want to call, so you can write `args[argument] = command` rather than `args[argument] = lambda: command`. – xnx Jan 19 '16 at 13:55
  • Yes, but even when doing that, the same error occurs. `TypeError: 'str' object is not callable` –  Jan 19 '16 at 13:56
  • Ah, I see: you're trying to call the keys to your dictionary instead of the values (which are the functions). See the top of my post. – xnx Jan 19 '16 at 14:00
  • 1
    OH MY GOD I LOVE YOU (**'nuff said**) –  Jan 19 '16 at 14:01
  • Now all I need to do is manually incorporate `argparse` into the mix. –  Jan 19 '16 at 14:02
0

Since in Python functions are first-class objects, you may create dictionary of {string: callable} pairs.

def help():
    print("Usage: xyz")


d = {
    'help': help,
    'version': lambda: print("1.0"), # lambda is also callable
}

After definition like that usage would be following:

my_function = d["help"]  # retrieves function stored under key 'help' in dict
my_function()  # calls function

# or obviously you may drop explainatory variable
d['help']()

It'll work for your custom solution with problem defined like that.

For standardized way of creating command line interfaces you may want to get familiar with argparse module.

Łukasz Rogalski
  • 22,092
  • 8
  • 59
  • 93
  • Could I modify this to something like: `addargument(arg, function)`, host the function in a different module and call it with `addargument("-v", argnamegoeshere)`? –  Jan 19 '16 at 13:58
-1

Python has a function called eval() that takes strings and tries to evaluate them as code.

>>> eval("2+3")
5

Then you can store your code as multi-line strings using 3 quotation marks

x = """the quick brown fox
jumps over the
lazy dogs"""

If you want to store the functions themselves, no reason why they can't also be placed in a dictionary. Even without lambdas.

def f():
    return 1

def g(x):
    return x + 1

def h(x):
    return 2*x

F = [f,g,h]
G = {"a": f, "b": g, "c": h}

(F[0](), G["a"]())

The value of the tuple is (1,1). Otherwise I don't know what you are asking for.

Community
  • 1
  • 1
john mangual
  • 7,718
  • 13
  • 56
  • 95
  • @frayment Python doesn't really do that. You can store *functions* or you can store the *code* as strings and evaluate them. – john mangual Jan 19 '16 at 13:37
  • Well the former is what I'm trying to achieve. –  Jan 19 '16 at 13:40
  • @frayment my example now includes a *list* of functions and a *dictionary* of functions. does that work? – john mangual Jan 19 '16 at 13:41
  • That last example is **partially** what I'm looking for. The problem is, is that you have hard-coded those values into place. I want to be able to call a function, which specifies my method to be run **as an argument**, and then dynamically store that argument in the dictionary. –  Jan 19 '16 at 13:46
  • @frayment maybe these methods are will be helpful to you. good luck! – john mangual Jan 19 '16 at 13:59