1

I have been looking for the answer to this for sometime but I always end up looking at tutorials covering the basics. My problem is -

If I have a function:

def foo(a, b, *args, **kwargs):

    if args[0]:
        if args[0] in range(1, 4):
            x=args[0]
        else:
            raise ValueError("Eww that is one ugly x!")
    else:
        x = kwargs.get('x', 3)
    if args[1]:
        if args[1] in ['some','list','of','strings']:
             y = args[1]
        else:
           raise ValueError("Invalid y")
    else:        
        y = kwargs.get('y', "some")

    if x == 1:
       print("Good")
    elif x == 2:
       print("Bad")
    elif x == 3:
       print("Clint")
    else:
       raise ValueError("Eww that is one ugly x!")

    if y == 'some':
       print(y + str(x))
    elif y == 'list':
       print("happy")
    elif y == 'of':
       print("healthy")
    elif y == 'strings':
       print(y + 'me')
    else:
       raise ValueError("Invalid y")

I am looking for a simpler way of treating args[0] and kwargs.get('x') as equivalent - insofar as I would like to perform the same type and value validation checks on which ever is assigned. In short how do I map the value of both args[i] and kwargs.get(k) to the same object.

Dan Temkin
  • 1,565
  • 1
  • 14
  • 18
  • Possible duplicate of [What does \*\* (double star/asterisk) and \* (star/asterisk) do for parameters?](https://stackoverflow.com/questions/36901/what-does-double-star-asterisk-and-star-asterisk-do-for-parameters) – bryanx Feb 03 '18 at 03:17
  • Why do you think you need to look in args[0] and kwargs for this value? Why isn't a simple x parameter the answer? – Ned Batchelder Feb 03 '18 at 11:05

3 Answers3

0

Assuming you really want your function to depend on args and kwargs, you could do a try/except statement (telling you if args[0] exist) and use the fact that your ValueError is the same for both cases.

def foo(*args, **kwargs):
    try:
        x = args[0] if args[0] in range(1,4) else None
    except:
        x = kwargs.get('x', 3)

    if x == 1:
       print("Good")
    elif x == 2:
       print("Bad")
    elif x == 3:
       print("Clint")
    else:
       raise ValueError("Eww that is one ugly x!")
Banana
  • 1,149
  • 7
  • 24
  • Getting x from args and kwargs like this is silly: it's the product of not understanding how Python functions work. Don't encourage it. – Ned Batchelder Feb 03 '18 at 03:37
  • I didn't encourage it, I even started with saying thats only if he really doesn't want to rewrite the function. It's silly but not problematic. – Banana Feb 03 '18 at 03:49
  • You're imagining he can rewrite the body of the function, but not the arguments? It's ridiculous. Don't invent nonsensical scenarios to justify writing absurd code. Being given a dict with an x key is a completely different question, and args and kwargs wouldn't apply anyway. – Ned Batchelder Feb 03 '18 at 03:49
  • 1
    Well, I changed that comment. Anyway I don't see what you're getting so worked up for. – Banana Feb 03 '18 at 03:49
  • People ask questions based on misunderstandings. That's natural, it's to be expected. But it bothers me that people who know better just go ahead and feed silly code to them, instead of teaching them where they misunderstood, so they can write good code. – Ned Batchelder Feb 03 '18 at 03:52
  • Q: "My house is too cold, how can I set it on fire?" A: "well, you can use a blow torch on the walls...." – Ned Batchelder Feb 03 '18 at 03:55
  • You already did explain the correct way, I just followed up with the idiocratic straightforward answer. I don't see a problem in knowing how to set your house on fire as long as you already know you shouldn't and there's a better warm to warm it up. – Banana Feb 03 '18 at 03:55
  • Your answer doesn't mention, "You shouldn't do this, there's a better way." – Ned Batchelder Feb 03 '18 at 04:18
  • And his question doesn't mention the part "My house is too cold" - you don't know the situation. I say "assuming you really want your function to depend on..", believing that one can infer from that and your post that usually there is a better way. Also this solution may be silly, but I don't see it being dangerous after all, you might want to explain why you think that is. – Banana Feb 03 '18 at 04:44
  • This actually isn't 'bad' per se, but I don't see how it solves my simplicity problem. I am trying to avoid writing a separate function to handle args/kwargs validation but is this really the only way to have both methods of assignment and ensure their types and/or values? – Dan Temkin Feb 03 '18 at 07:23
  • @DanTemkin hmm I'm not sure I fully understand but it sounds like you could write dictionaries to simplify your code at least, but that is not really a better solution, just more aesthetic. One dictionary with possible keys and objects you want them to be contained in. You could use another nested dictionary for the allowed values and their prints, but thats also not very clean I think.. Can you maybe provide a more concrete example of what you might/do want to do/use this for? – Banana Feb 03 '18 at 09:51
  • `def foo(x):` lets you use both forms of calling the function. – Ned Batchelder Feb 03 '18 at 11:07
0

I'm curious what your real question is?

If I were simply trying to improve upon your example, I think I would choose the following approach.

from typing import Tuple, Dict, Iterable, Any


def value_from_args_or_kwargs(args: Tuple, index: int, cond: Iterable, error_msg: str,
                              kwargs: Dict, key: str, val: Any):
    if len(args) - 1 >= index and args[index]:
        return args[index] if args[index] in cond else ValueError(error_msg)
    else:
        return kwargs.get(key, val)


def bar(a, b, *args, **kwargs):
    x = value_from_args_or_kwargs(args, 0, range(1, 4), 'Eww that is one ugly x!',
                                  kwargs, 'x', 3)
    y = value_from_args_or_kwargs(args, 1, ['some', 'list', 'of', 'strings'], 'Invalid y',
                                  kwargs, 'y', 'some')
    print('Good') if x == 1 else \
        print('Bad') if x == 2 else \
        print('Client') if x == 3 else ValueError('Eww that is one ugly x!')

    print(f'{y}{x}') if y == 'some' else \
        print('happy') if y == 'list' else \
        print('healthy') if y == 'of' else \
        print(f'{y}me') if y == 'strings' else ValueError('Invalid y')
Carson
  • 6,105
  • 2
  • 37
  • 45
-1

You should just define your function like this:

def foo(x):
    if x == 1:
        print("Good")
    elif x == 2:
        print("Bad")
    elif x == 3:
        print("Clint")
    else:
        raise ValueError("Eww that is one ugly x!")

Now you can call this function like:

foo(3)

or as:

foo(x=3)

There's no need to use *args or **kwargs if you know exactly what arguments you are expecting.

If you need more arguments, then just name them:

def foo(x, y, z, a, b, c):
    ...

There's no need for *args or **kwargs if you know what arguments you are expecting.

Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
  • Right, so I understand where you are coming from. In my "real-life scenario" I was using *args and **kwargs to contain arguments that were optional for the user and I was trying to avoid was writing a function that spans multiple lines due to the immense arguments alone and setting the defaults within the function definition. I also understand that if I know the argument I could specify it directly however, why would you ever write a function to purposefully accept an argument and value you didn't expect, **kwargs or no **kwargs that seems silly, no? – Dan Temkin Feb 03 '18 at 06:52
  • Saving lines in the header just to add more of them in the body seems like the wrong way to go. If your function has 10 arguments, just list them out, and you have them by name. Using args and kwargs just obscures what the function does. Using explicit names in the def line gives you a readable function signature. – Ned Batchelder Feb 03 '18 at 11:10
  • "Why would you write a function to accept an argument you didn't expect?" `args` is good if you know what the values mean, but you don't know how many there will be. `kwargs` is good if you want your function to be a proxy for another function, and you will pass through the arguments, or if you can accept arbitrary key/value pairs from the caller, like `dict(a=1, b=2)`. – Ned Batchelder Feb 03 '18 at 11:15