Formatting arguments into dictionary

So I have a function:

def func(a, b, c):
    ... # ?????????
    return attribute_dict

print(func(1, 2, 3))
# {'a': 1, 'b': 2, 'c': 3}

Is there any way to return this without needing to manually type 'a', 'b', 'c' and also not using **kwds instead?

If the function is only doing that, it’s trivial:

>>> def func(a, b, c):
...     return locals()
... 
>>> func(1, 2, 3)
{'a': 1, 'b': 2, 'c': 3}

If you need to figure out which of the locals() are the parameter names, you’ll need to do introspection, for example:

import inspect

def func(a, b, c):
    d = None
    return {
        k: v for k, v in locals().items()
        if k in inspect.getargs(func.__code__).args
    }

Or maybe set up a decorator, so that the wrapper handles the simple case. But then you’ll have to design the way that the decorator holds on to and communicates back the determined locals() result.

1 Like

Thank you.

The function is more complex. So locals() is not an option.

I am looking for something simpler without using inspect.

It is a reoccurring need of mine which I haven’t found solution for yet.

Even if to implement something for it. I don’t even know what could be used for this.

Why not inspect? Have you tried inspect.signature() and friends?

For example:

>>> def func(a, b, c=42, *, d, e='E'):
...     f = "not a param"
...     ...
...     bound_arguments = func_sig.bind(**{
...         name: val
...         for name, val in locals().items()
...         if name in func_sig.parameters
...     })
...     return dict(bound_arguments.arguments)
...     
>>> func_sig = inspect.signature(func)
>>> func(1, b=2, d=7)
{'a': 1, 'b': 2, 'c': 42, 'd': 7, 'e': 'E'}
>>> func(a=1, b=2, c=3, d=7)
{'a': 1, 'b': 2, 'c': 3, 'd': 7, 'e': 'E'}
>>> func(1, 2, 3, d=7, e=8)
{'a': 1, 'b': 2, 'c': 3, 'd': 7, 'e': 8}
1 Like

Ah, right, was too busy trying to figure out the workaround that I forgot the direct way :slight_smile:

But yes, I really have to question why this is a reoccurring need.

Simply too slow and not simple enough.

The way I work around this at the moment is:

NAMES = ['a', 'b', 'c']
def foo(a, b, c):
    return dict(zip(NAMES, [a, b, c]))

I am looking for an alternative to this, so desirable solution would ideally improve on this.

So simplicity is satisfiable for the above, but maintenance is a bit annoying as need to keep 2 lists in sync.

It is not this specific case, but a class of problems that I can not seem to find elegant simple way to approach.

See Mailman 3 Search results for "nameof" - Python-ideas - python.org for examples for nameof use cases.

This problem here is nameof class problem, but such that even nameof can not solve:

print([nameof(i) for i in [a, b, c]])
# ['i', 'i', 'i']

So I am just trying to get some opinions, different POVs and possibly solutions that I can not see myself.

If your goal is to just collect the named parameters, you can easily do this with locals, even if you are using other variables, just make sure it’s the first thing you do in the function:

def foo(a, b, c):
    args = dict(locals())
    # ... rest of the function
    return args

What are the situations where this is an impractical solution?

Oh, I guess that solution doesn’t quite work because locals is going to include closure locals and not just the arguments. But still, I am pretty sure this covers most of the already very few situations where this would be helpful.

This is possibly as good as it can get for what I need this for at the moment.

Otherwise, here is a completely general function that only uses sys._getframe:

def get_args():
    frame = sys._getframe(1)
    total_argcount = frame.f_code.co_argcount + frame.f_code.co_kwonlyargcount
    total_argcount += bool(frame.f_code.co_flags & 4) # Check if we have *args
    total_argcount += bool(frame.f_code.co_flags & 8) # Check if we have **kwargs
    return {
        name: frame.f_locals[name]
        for name in frame.f_code.co_varnames[:total_argcount]
    }

This is less cross-version compatible than a solution using inspect but that is unavoidable because there is no stability guarantees about these interfaces. But AFAIK, this should work for all currently active python versions.

1 Like

Had you considered writing a decorator? It would inspect the function
signature (just once when the function gets defined) and wrap it in
logic to produce the dict at runtime. It even preprovide that dict as an
additional argument. It’s take a little effort to make it, but once
you’ve got it…

3 Likes

Do you need it to work with arbitrary number of function arguments?
If not, I think this would work:

>>> def f(a, b, c):
...     return dict(a=a, b=b, c=c)
...
>>> f(1, 2, 3)
{'a': 1, 'b': 2, 'c': 3}