0

I have a routine that will take a list of tuples in the form:

(function, [optional] arguments, [optional] keyword arguments)

as part of a Command design pattern implementation.

ORG1_PROCESS = [(add_header_row, df, COLUMN_LABELS['ORG1']),
                (func_1, {'abc' = 123}), 
                (func_2), ]
ORG2_PROCESS = [(add_header_row, df, COLUMN_LABELS['ORG2']), ]

def process_commands(a_list: List[Tuple[Any, ...]]) -> None:
    for item in a_list:
        (func, *args, **kwargs) = item
        if kwargs:
            func(*args, **kwargs)
        elif args:
            func(*args)
        else:
            func()

I want to execute like this:

process_commands(ORG1_PROCESS)

to execute the equivalent of:

add_header_row(df, COLUMN_LABELS['ORG1'])
func_1(abc=123)
func_2()

However, I have a syntax error:

    (func, *args, **kwargs) = item
                  ^
    SyntaxError: invalid syntax

I thought that is valid tuple unpacking in Python 3.9.7. Any advice on what am I missing here would be great!

user1330734
  • 390
  • 6
  • 21
  • What would you expect `item` to look like to make that a valid syntax? – Timus May 01 '23 at 11:01
  • Please check the syntax of the [assignment statement in Python 3.9](https://docs.python.org/3.9/reference/simple_stmts.html?#assignment-statements), where the [`target`](https://docs.python.org/3.9/reference/simple_stmts.html?#grammar-token-target) does not include dictionary unpacking. – Mechanic Pig May 01 '23 at 11:09
  • The second argument in your second example (a tuple with `func_1`) cannot distinguish whether it is a positional argument or a keyword argument. That is, it is impossible to unpack it. A practical solution is not to make them optional. Instead, use an empty tuple and an empty dictionary, such as: `(func1, (), {})`. Then you can unpack like this: `f, args, kwargs = item`. – ken May 01 '23 at 11:15

1 Answers1

0

Thanks for the pointers guys, the code now works as expected.

def do_commands(commands: List[Tuple[Any, ...]]) -> None:
    for command in commands:
        func, *args = command
        if args and isinstance(args[-1], dict):
            kwargs = args[-1]
            args = args[:-1]  # Remove the kwargs dict from args
            func(*args, **kwargs)
        elif args:
            func(*args)
        else:
            func()

It checks to see if the last passed argument is a dictionary and treats it as kwargs if so. Note that the code will not handle the condition where a function takes no arguments, but is passed in with args and/or kwargs within a tuple.

Here's the output:

>>> def func(**kwargs):
...     print(f'Running func() {kwargs}')
...
>>> def func1(*args, **kwargs):
...     print(f'Running func1() {args} {kwargs}')
... 
>>> def func2(*args, **kwargs):
...     print(f'Running func2() {args} {kwargs}')
>>> COMMAND_LIST = [(func, {'abc':123}), (func2, 1, 2, {'def': 345}, 4, 5), (func,), (func2,)]
>>> do_commands(COMMAND_LIST)
Running func() {'abc': 123}
Running func2() (1, 2, {'def': 345}, 4, 5) {}
Running func() {}
Running func2() () {}
user1330734
  • 390
  • 6
  • 21