3

First time posting here, so apologies if I don't follow formatting guidelines or anything.

I'm writing a terminal-like utility tool for, among other things, bulk file editing. My goal was to have every function be three letters ling, a bit like in Assembly. For example, mkd("yeet") makes a directory called yeet.
The basic way it works is as follows: I define a whole bunch of functions, and then I set up a while True loop, that prints the eval() of whatever I type. So far, everything's going pretty well, except for one thing. I want to be able to call functions without having to add the brackets. Any parameters should be added afterwards, like using sys.argscv[1].

Here is a link to the GitHub repo.

Is this possible in Python, if so, how?

Obviously, just typing the name of the function will return me <function pwd at 0x7f6c4d86f6a8> or something along those lines.

Thanks in advance,
Walrus Gumboot

  • You should almost **never** use `eval(input())`! That will allow the user to do many bad things (https://stackoverflow.com/questions/1832940/why-is-using-eval-a-bad-practice, https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html). – Ted Klein Bergman Apr 17 '19 at 10:42
  • I think you're going about it the wrong way. You should parse the input string and check it's valid by doing string comparisons and then call the appropriate function (or return an error message). – Ted Klein Bergman Apr 17 '19 at 10:43
  • Just to clarify; you're not asking how to call a function without paranthesis in Python, but how a user could type in `mdk yeet` into your program? – Ted Klein Bergman Apr 17 '19 at 10:46
  • @TedKleinBergman Exactly. Also, can I make `eval()` safer by adding `{'__builtins__':None}, {}` or does this make no difference? – Walrus Gumboot Apr 17 '19 at 10:55
  • I would suggest not using `eval` at all, as it will guide you towards both security issues and performance issues. A better approach would be to take the input from the user and parse it yourself, then checking if that method exists. – Ted Klein Bergman Apr 17 '19 at 10:58
  • Agreed. The cmd module exists for exactly this purpose, and it's much safer. See example below. – brunns Apr 17 '19 at 10:59

5 Answers5

3

You can use locals or globals depending the scope and and pass the string you get in the arguments:

>>> def foo():
...     print("Hi from foo!")
... 
>>> locals()["foo"]()
Hi from foo!

Where "foo" would be the sys.args[1] from the call to python script.py foo

Or you can define your own dictionary with the available commands, for example:

calc.py

commands = {
    "add" : lambda x, y: x + y,
    "mul" : lambda x, y: x * y,
}

if __name__ == __main__:
    import sys
    _, command, arg1, arg2, *_ = sys.args
    f = commands.get(command, lambda *_: "Invalid command")
    res = f(int(arg1), int(arg2))
    print(res)

Calling python calc.py add 5 10

Netwave
  • 40,134
  • 6
  • 50
  • 93
1

In Python, no, you do need the brackets to call a function.

You might find the cmd module helpful, though - it's intended for writing interactive command line tools like you're describing, and you'd have a lot more flexibility as to the input format.

Using cmd, you tool might look like:

import cmd, os

class MyTerminalLikeUtilityTool(cmd.Cmd):

    def do_mkd(self, line):
        os.makedirs(line)

    def do_EOF(self, line):
        return True

if __name__ == '__main__':
    MyTerminalLikeUtilityTool().cmdloop()
brunns
  • 2,689
  • 1
  • 13
  • 24
1

Here's a simple example if you want to parse the string yourself, that's easily extendable.

def mkd(*args):
    if len(args) == 1:
        print("Making", *args)
    else:
        print("mdk only takes 1 argument.")

def pwd(*args):
    if len(args) == 0:
        print("You're here!")
    else:
        print("pwd doesn't take any arguments.")

def add(*args):
    if len(args) == 2:
        if args[0].isdigit() and args[1].isdigit(): 
            print("Result:", int(args[0]) + int(args[1]))
        else:
            print("Can only add two numbers!")
    else:
        print("add takes exactly 2 arguments.")

def exit(*args):
    quit()


functions = {'mkd': mkd, 'pwd': pwd, 'add': add, 'exit': exit}  # The available functions.  

while True:
    command, *arguments = input("> ").strip().split(' ')  # Take the user's input and split on spaces.
    if command not in functions:
        print("Couldn't find command", command)
    else:
        functions[command](*arguments)
Ted Klein Bergman
  • 9,146
  • 4
  • 29
  • 50
  • @WalrusGumboot Do note however that the arguments are just strings, which means you have to do some error handling if you need to convert them to integers (which eval did automatically). Either surround the statement in a try-catch, or check that the arguments really are numbers before converting them. Added an example in the answer. – Ted Klein Bergman Apr 17 '19 at 11:37
0

The click library is a library that i have used and in my opinion, perfect for creating commandline applications. Here is a youtube video showing how to create an example application.

Here is an explanation from the site on how click does not use argeparse.

Nick
  • 3,454
  • 6
  • 33
  • 56
0

No, it's not possible. There's no valid syntax having the form

f 1 2

in python. You need either a language that supports that (eg. Scala) or a language that has macros (eg. lisp/clojure)

The closest thing you could do is eg, make the function a callable object, overload the binary shift operator, and write something like

f << 1, 2
blue_note
  • 27,712
  • 9
  • 72
  • 90