2

I have the following code:

from functools import partial

def create_droplet(args):
  print(args)

def droplets():
  print("test")

commands = {
  'create_droplet': partial(create_droplet),
}

command_in = input()
command = command_in.split(" ")[:1][0]

if command in commands:
  args = command_in.split(" ")[1:]
  commands[command](args)

What I want to do is allow droplets() to be called by adding 'droplets': droplet to commands, but since it doesn't take any arguments I get TypeError: droplets() takes 0 positional arguments but 1 was given.

martineau
  • 119,623
  • 25
  • 170
  • 301
Jordan Baron
  • 3,752
  • 4
  • 15
  • 26

3 Answers3

3

You can always define droplets with an unused parameter:

def droplets(unused_args):
    print("test")

There is no big deal, it's common practice in these situations where you have some instances whose behaviour depends on the input and others whose behaviour does not.

If you really want droplets to take no argument you can always write a wrapper that calls it:

def droplets_wrapper(unused_arg):
    droplets()

And put droplets_wrapper in your commands dictionary.


If you want to be able to both call droplets(whatever) and also droplets() then just use a default argument:

def droplets(unused_args=None):
    print("test")
Bakuriu
  • 98,325
  • 22
  • 197
  • 231
2

Just define a lambda that ignores the argument and calls droplets():

commands = {
  'create_droplet': partial(create_droplet),
  'droplets': (lambda _ : droplets())
}
Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
2

The most scalable solution would be to standardize the signature of functions in commands. Do so by making all functions take any number of arguments with *args and let them handle the argument parsing.

The advantage of this approach is that it will easily generalize when you will need to add a function that takes, say, 2 arguments to your dict and allow your API to gracefully display custom error messages. This even extends to functions that need keyword arguments using **kwargs.

def create_droplet(*args):
    print(args)

def droplets(*args):
    # Here you can warn the user if arguments are given
    if args:
        print('command droplets takes no argument')
        return

    print("test")

commands = {
  'create_droplet': create_droplet,
  'droplets': droplets
}

# Here is a one-line way to extract command and arguments
command, *args = input('enter a command: ').split()

if command in commands:
    commands[command](*args)
else:
    print('command does not exist')

Example of outputs:

enter a command: create_droplet 1 2
('1', '2')

enter a command: droplets
test

enter a command: droplets 1
command droplets takes no argument
Olivier Melançon
  • 21,584
  • 4
  • 41
  • 73
  • what does `*args` mean? Is it just a naming convention? If so, why can't I just use `args`? – Jordan Baron May 18 '18 at 18:05
  • The * in *args makes it wrapped arguments. This is a way to receive any number of argument as a tuple. You can then wrap and unwrap the arguments with the * notation. I usually see that as being the standard way to approach your kind of problem. You can read more about wrapped arguments [here](https://stackoverflow.com/questions/36901/what-does-double-star-asterisk-and-star-asterisk-do-for-parameters). – Olivier Melançon May 18 '18 at 18:08