What you want to do is impossible. You can never write this in Python:
shoppingcart(item='laptop', 100,200,300)
In either 2.x or 3.x, you will get an error:
SyntaxError: non-keyword arg after keyword arg
First, a little background:
A "keyword" argument is something like item='laptop', where you specify the name and value. A "positional", or "non-keyword" argument, is something like 100, where you just specify the value. You have to put the keyword args after all of the positional args whenever you call a function. So, you can write shoppingcart(100, 200, 300, item='laptop'), but you can't write shoppingcart(item='laptop', 100, 200, 300).
This makes a little more sense when you understand the way keyword arguments are handled. Let's take a very simple case, with a fixed set of parameters and no default values, to make everything easier:
def shoppingcart(item, price):
print item, price
Whether I call this with shoppingcart('laptop', 200), shoppingcart(price=200, item='laptop'), or any other possibility, you can figure out how the arguments arrive in item and price in the function body. Except for shoppingcart(price=200, 'laptop'), Python can't tell what laptop is supposed to be—it's not the first argument, it's not given the explicit keyword item, so it can't be the item. But it also can't be the price, because I've already got that. And if you think it through, it's going to have the same problem any time I try to pass a keyword argument before a positional argument. In more complex cases, it's harder to see why, but it doesn't even work in the simplest case.
So, Python will raise a SyntaxError: non-keyword arg after keyword arg, without even looking at how your function was defined. So there's no way to change your function definition to make this work; it's just not possible.
But if you moved item to the end of the parameter list, then I could call shoppingcart(100, 200, 300, item='computer') and everything would be fine, right? Unfortunately, no, because you're using *args to soak up the 100, 200, 300, and you can't put any explicit parameters after a *args. As it turns out, there's no principled reason for this restriction, so they did away with it in Python 3—but if you're still using 2.7, you have to live with it. But at least there is a workaround.
The way around this is to use **kwargs to soak up all of the keyword arguments and parse them yourself, the same way you used *args to soak up all the positional arguments. Just as *args gets you a list with the positional arguments, **kwargs gets you a dict with each keyword argument's keyword mapped to its value.
So:
>>> def shoppingcart(*args, **kwargs):
... print args, kwargs
>>> shoppingcart(100, 200, 300, item='laptop')
[100, 200, 300], {'item': 'laptop'}
If you want item to be mandatory, you just access it like this:
item = kwargs['item']
If you want it to be optional, with a default value, you do this:
item = kwargs.get('item', 'computer')
But there's a problem here: You only wanted to accept an optional item argument, but now you're actually accepting any keyword argument. If you try this with a simple function, you get an error:
>>> def simplefunc(a, b, c):
... print a, b, c
>>> simplefunc(1, 2, 3, 4)
TypeError: simplefunc() takes exactly 3 arguments (4 given)
>>> simplefunc(a=1, b=2, c=3, d=4, e=5)
TypeError: simplefunc() got an unexpected keyword argument 'd'
There's a very easy way to simulate this with **kwargs. Remember that it's just a normal dict, so if you take out the expected keywords as you parse them, there should be nothing left over—if there is, the caller passed an unexpected keyword argument. And if you don't want that, you can raise an error. You could write that like this:
def shoppingcart(*prices, **kwargs):
for key in kwargs:
if key != 'item':
raise TypeError("shoppingcart() got unexpected keyword argument(s) '%s' % kwargs.keys()[0]`)
item = kwargs.get('item', 'computer')
print item, prices
However, there's a handy shortcut. The dict.pop method acts just like dict.get, but in addition to returning the value to you, it removes it from the dictionary. And after you've removed item (if it's present), if there's anything left, it's an error. So, you can just do this:
def shoppingcart(*args, **kwargs):
item = kwargs.pop('item', 'computer')
if kwargs: # Something else was in there besides item!
raise TypeError("shoppingcart() got unexpected keyword argument(s) '%s' % kwargs.keys()[0]`)
Unless you're building a reusable library, you can get away with something simpler than that whole if/raise thing and just do assert not kwargs; nobody will care that you get an AssertionError instead of a TypeError. That's what lqc's solution is doing.
To recap, it doesn't matter how you write your function; it can never be called the way you want. But if you're willing to move item to the end, you can both write it and call it in a reasonable way (even if it's not as simple or readable as the Python 3 version).
For some rare cases, there are other ways to go. For example, if prices always have to be numbers, and items are always strings, you can change your code to do something like this:
def shoppingcart(*prices):
if prices and isinstance(prices[0], basestring):
item = prices.pop(0)
else:
item = 'computer'
print item
for price in prices:
print price
Now, you can do this:
>>> shoppingcart(100, 200, 300)
computer
100
200
300
>>> shoppingcart('laptop', 100, 200, 300) # notice no `item=` here
laptop
100
200
300
This is basically the kind of trick Python uses to implement slice(start=None, stop, step=None), and it is very occasionally useful in your code. However, it makes your implementation much more fragile, makes the behavior of your function much less obvious to the reader, and generally just doesn't feel Pythonic. So, it's almost always best to avoid this and just make the caller use real keyword arguments if that's what you want, with the restriction that they have to come at the end.
shoppingcart(item='laptop', 100,200,300). It doesn't matter how you write the function, because you just can't call it that way.item='laptop', where you specify the name and value; a "positional arg' or "non-keyword arg" is something like100, where you just specify the value. You have to put the keyword args after all of the positional args whenever you call a function. So, you can writeshoppingcart(100, 200, 300, item='laptop')—and lqc's code will work if you do that.