1

If I have a function

def func(a=1, b=2, c=3, d=4, e=5, f=6, g=7):
    return a * b * c * d * e * f * g

and I want extend this to have the entire alphabet as keyword arguments, is there a way to do that without having the arguments cause the line with def wrap through multiple lines while also not using the black box of **kwargs? In other words, I'd like a way to generate a list or dict of kwargs and their defaults that still tells the function explicitly what kwargs are allowed.

Chad
  • 1,434
  • 1
  • 15
  • 30
  • 3
    seems like a good way to do it would be to use `**kwargs` and then analyze it's contents. Throw exceptions for arguments that violate a set of rules, and sets default values for missing keys. – Warlax56 Jan 20 '22 at 16:03
  • 2
    You _could_ check `kwargs` to ensure it doesn't contain any keys you aren't expecting and throw an exception if it does, but I'm not sure the solution isn't worse than the problem. – Chris Jan 20 '22 at 16:03
  • 2
    How would you propose calling such a function? **kwargs might be a solution for the function implementation but when you call that function you'll have a very long line of code. Surely a better solution is to build a dictionary and pass its reference to the function. Thus both the calling code and the function implementation will look 'neat' – DarkKnight Jan 20 '22 at 16:07
  • Possible duplicate: https://stackoverflow.com/q/26987418/17457042 – azelcer Jan 20 '22 at 16:31

2 Answers2

1

My understanding of the use-case is that a function needs to be written that takes keywords which are named after every letter of the alphabet (I assume English/ASCII) - i.e., a-z

There is a need for validation of the argument(s) being passed.

The code should be concise.

Here's an idea based on the OP's code fragment...

from string import ascii_lowercase
import inspect

def func(d):
    for k in d.keys():
        if k not in ascii_lowercase:
            raise TypeError(f"{inspect.stack()[0].function}() got an unexpected keyword argument '{k}'")
    r = 1
    for c in 'abcdefg':
        r *= d.get(c, 1)
    return r

d = {k: v for v, k in enumerate('abcdefg', 1)}

print(func(d))

func() takes a single argument to a dictionary. That dictionary contains keys which must be in the range a-z. Not all values are needed.

func() checks the keys to ensure that they are all part of the a-z range. If not, it simulates the same Exception that would arise if a named parameter was passed that had not been declared as valid/recognised.

func() wants to multiply the values assigned to a-g and return the result.

In the main flow of the program, a dictionary is created thus:

{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7}

The result, as expected is, 5040.

If the dictionary were to contain, for example,

{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7, 'P':0}

...then the following would be emitted:

TypeError: func() got an unexpected keyword argument 'P'

Note:

Requires Python 3.5+

DarkKnight
  • 19,739
  • 3
  • 6
  • 22
1

You could write a (somewhat convoluted) decorator to provide the arguments and their default value as signature to the function:

import inspect
def signature(params): 
    arglist = ",".join(f"{k}={v.__repr__()}" for k,v in params.items())
    def addSignature(f):
        names = dict()
        code  = inspect.getsource(f).split("):\n")[-1]
        exec(f"def f({arglist}):\n"+code,names)
        return names['f']
    return addSignature

usage:

@signature(dict(zip("abcdefghijklmnopqrstuvwxyz",range(1,27))))
def myFunc():
    print(locals())

myFunc(4,5) # all arguments are available as local variables

{'a': 4, 'b': 5, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7, 'h': 8, 'i': 9,
 'j': 10, 'k': 11, 'l': 12, 'm': 13, 'n': 14, 'o': 15, 'p': 16, 'q': 17,
 'r': 18, 's': 19, 't': 20, 'u': 21, 'v': 22, 'w': 23, 'x': 24, 'y': 25,
 'z': 26}

inspect.signature(myFunc)
<Signature (a=1, b=2, c=3, d=4, e=5, f=6, g=7, h=8, i=9, j=10, k=11, 
l=12, m=13, n=14, o=15, p=16, q=17, r=18, s=19, t=20, u=21, v=22, w=23, 
x=24, y=25, z=26)>

The decorator essentially rewrites the function with the signature assembled from string representations of the dictionary key/values.

Alain T.
  • 40,517
  • 4
  • 31
  • 51